Custom JPanel cell with JButtons in JTable
2012-05-16 15:57
183 查看
If you ever wanted to add a JPanel with various interactive components (e.g. JButtons, JCheckBoxes etc.) in a JTable cell and could not figure out how to make them work, then this post is for you. Otherwise, continue googling ;)
no idea how to look for it.
JPanel in JTable?
JPanel with buttons in JTable?
JTable JPanel JButton?
OK, to be fair, I’m pretty sure my answer is in there somewhere deep (as in, after the first 3 results). But, as any impatient person that respects himself, I went the easy way: StackOverflow:JTable:
Buttons in Custom Panel in Cell.
You see, most examples I found with Google talked about Cell Editors, but for simple column components, such as using a JTextField to edit an integer, or a JComboBox to edit a String etc. I wanted a custom JPanel that did not change, but simply had interactive
controls.
In retrospect, I could have simply used a panel with MigLayout, but NO! I wanted the challenge. I wanted to learn how to do it. Plus, now I can sort my table (which is absolutely useless in my context).
Anyway, on with the code!
Yeah yeah, make the fields private, add getters/setters blah blah blah. Here is a simple JFrame that displays a JTable with some example data:
Here we create two RssFeeds with articles on the fly and add them to a JTable. If we run this example, we will see that each row displays the toString() value of our RssFeed object.
JTable without a Cell Renderer
This is the default behavior of JTable when the data is not a known class (Strings, ints, etc.) We can change this behavior by creating a custom TableModel. Here is a basic table model for our example:
We then simply change our table declaration to:
Voila! We now see the exact same thing in our table!
JTable with Table Model (but still no Cell Renderer)
Errr.. OK, so it turns out that it’s not the Model that converts our objects into Strings, but the Renderer. Each JTable has a TableCellRenderer that, given an object, it returns a Swing component that can be used to display the underlying data.
So now we need to tell the JTable how to convert an RssFeed into a Component. We can do this by implementing the TableCellRenderer like so:
We also need to register this renderer in our table:
In the above lines, we effectively say “Whenever a column is an RssFeed class, use this renderer”. We already said that our one and only column is of type RssFeed in our table model:
Now if we run our application, we can finally see something useful (although, I still prefer reading “com.pekalicious.interactiveJPanelTableCell.data.RssFeed@106caf16″. Yes, I’m THAT good):
JTable with Renderer
There are two things that are wrong here: first, every time the JTable needs to render a cell, it will instantiate a new JPanel. This can harm the performance if there are many rows. Second, the button does not work! Cell renderers are used only for what
they say, to render cells. They do not provide any mechanism for the underlying components. However, JTable also has Cell Editors. Cell Editors work pretty much the same way as Renderers, they take an Object and return a Component, but the component returned
can be interactive.
As you have probably guessed already, you can have different components returned by renderers and editors. An int, for example, can be rendered using a JLabel and edited using a JTextField (which is the default behavior of JTable). However, in our example,
we want the same panel both for editing and rendering. This is why we will create a common Component that will be used by both:
Now, our cell renderer becomes:
And once again, nothing changed! But we did optimize (and that’s always good, right?). Now, there is only one instance of RssFeedCellComponent which changes it’s components depending on the RssFeed passed. In addition, creating a Cell Editor that returns
the same component is now pretty simple:
And now we register our editor:
We can do even better. We can combine all three classes, RssFeedCellComponent, RssFeedCellRenderer and RssFeedCellEditor into a single RssFeedCell:
Now we use only one Panel for all rendering and editing!
JTable with Renderer and Editor
Anyway, that’s that. As usual, you can find the source and the executable in myJava Corner.
Now excuse me, but I got some Void Rays to counter attack. En Taro Adun!
The Intro
It’s really difficult to find something in the Internet when you are not really sure how to phrase it. That’s why you need to practice your google-fu as much as possible. See, I was looking for a way to have a JPanel with buttons in a JTable cell, but hadno idea how to look for it.
JPanel in JTable?
JPanel with buttons in JTable?
JTable JPanel JButton?
OK, to be fair, I’m pretty sure my answer is in there somewhere deep (as in, after the first 3 results). But, as any impatient person that respects himself, I went the easy way: StackOverflow:JTable:
Buttons in Custom Panel in Cell.
You see, most examples I found with Google talked about Cell Editors, but for simple column components, such as using a JTextField to edit an integer, or a JComboBox to edit a String etc. I wanted a custom JPanel that did not change, but simply had interactive
controls.
In retrospect, I could have simply used a panel with MigLayout, but NO! I wanted the challenge. I wanted to learn how to do it. Plus, now I can sort my table (which is absolutely useless in my context).
Anyway, on with the code!
The Code
First thing’s first, we need to have a basic ADT that will contain some data. This is what we ultimately want to display in our table rows. Let’s say we want to have some RSS Feeds.public class RssFeed { public String name; public String url; public Article[] articles; public RssFeed(String name, String url, Article[] articles) { this.name = name; this.url = url; this.articles = articles; } } public class Article { public String title; public String url; public String content; public Article(String title, String url, String content) { this.title = title; this.url = url; this.content = content; } }
Yeah yeah, make the fields private, add getters/setters blah blah blah. Here is a simple JFrame that displays a JTable with some example data:
public class JInteractiveTableExample extends JFrame { public JInteractiveTableExample() { super("Interactive Table Cell Example"); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 300); List<RssFeed> feeds = new ArrayList<RssFeed>(); feeds.add(new RssFeed("pekalicious", "http://feeds2.feedburner.com/pekalicious", new Article[] { new Article("Title1", "http://title1.com", "Content 1"), new Article("Title2", "http://title2.com", "Content 2"), new Article("Title3", "http://title3.com", "Content 3"), new Article("Title4", "http://title4.com", "Content 4"), })); feeds.add(new RssFeed("Various Thoughts on Photography", "http://various-photography-thoughts.blogspot.com/feeds/posts/default", new Article[] { new Article("Title1", "http://title1.com", "Content 1"), new Article("Title2", "http://title2.com", "Content 2"), new Article("Title3", "http://title3.com", "Content 3"), new Article("Title4", "http://title4.com", "Content 4"), })); JTable table = new JTable( new Object[][] { new RssFeed[] { feeds.get(0) }, new RssFeed[] { feeds.get(1) } }, new String[] { "Feeds" } ); add(new JScrollPane(table)); } }
Here we create two RssFeeds with articles on the fly and add them to a JTable. If we run this example, we will see that each row displays the toString() value of our RssFeed object.
JTable without a Cell Renderer
This is the default behavior of JTable when the data is not a known class (Strings, ints, etc.) We can change this behavior by creating a custom TableModel. Here is a basic table model for our example:
public class RssFeedTableModel extends AbstractTableModel { List feeds; public RssFeedTableModel(List feeds) { this.feeds = feeds; } public Class getColumnClass(int columnIndex) { return RssFeed.class; } public int getColumnCount() { return 1; } public String getColumnName(int columnIndex) { return "Feed"; } public int getRowCount() { return (feeds == null) ? 0 : feeds.size(); } public Object getValueAt(int rowIndex, int columnIndex) { return (feeds == null) ? null : feeds.get(rowIndex); } public boolean isCellEditable(int columnIndex, int rowIndex) { return true; } }
We then simply change our table declaration to:
JTable table = new JTable(new RssFeedTableModel(feeds));
Voila! We now see the exact same thing in our table!
JTable with Table Model (but still no Cell Renderer)
Errr.. OK, so it turns out that it’s not the Model that converts our objects into Strings, but the Renderer. Each JTable has a TableCellRenderer that, given an object, it returns a Swing component that can be used to display the underlying data.
So now we need to tell the JTable how to convert an RssFeed into a Component. We can do this by implementing the TableCellRenderer like so:
public class RssFeedCellRenderer implements TableCellRenderer{ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { RssFeed feed = (RssFeed)value; JButton showButton = new JButton("View Articles"); showButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { JOptionPane.showMessageDialog(null, "HA-HA!"); } }); JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); panel.add(new JLabel("<strong>" + feed.name + "</strong>" + feed.url + "Articles " + feed.articles.length + "")); panel.add(showButton); if (isSelected) { panel.setBackground(table.getSelectionBackground()); }else{ panel.setBackground(table.getSelectionForeground()); } return panel; } }
We also need to register this renderer in our table:
JTable table = new JTable(new RssFeedTableModel(feeds)); table.setDefaultRenderer(RssFeed.class, new RssFeedCellRenderer()); table.setRowHeight(60);
In the above lines, we effectively say “Whenever a column is an RssFeed class, use this renderer”. We already said that our one and only column is of type RssFeed in our table model:
public Class getColumnClass(int columnIndex) { return RssFeed.class; }
Now if we run our application, we can finally see something useful (although, I still prefer reading “com.pekalicious.interactiveJPanelTableCell.data.RssFeed@106caf16″. Yes, I’m THAT good):
JTable with Renderer
There are two things that are wrong here: first, every time the JTable needs to render a cell, it will instantiate a new JPanel. This can harm the performance if there are many rows. Second, the button does not work! Cell renderers are used only for what
they say, to render cells. They do not provide any mechanism for the underlying components. However, JTable also has Cell Editors. Cell Editors work pretty much the same way as Renderers, they take an Object and return a Component, but the component returned
can be interactive.
As you have probably guessed already, you can have different components returned by renderers and editors. An int, for example, can be rendered using a JLabel and edited using a JTextField (which is the default behavior of JTable). However, in our example,
we want the same panel both for editing and rendering. This is why we will create a common Component that will be used by both:
public class RssFeedCellComponent extends JPanel { RssFeed feed; JButton showButton; JLabel text; public RssFeedCellComponent() { showButton = new JButton("View Articles"); showButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { JOptionPane.showMessageDialog(null, "Reading " + feed.name); } }); text = new JLabel(); add(text); add(showButton); } public void updateData(RssFeed feed, boolean isSelected, JTable table) { this.feed = feed; text.setText("<strong>" + feed.name + "</strong>" + feed.url + "Articles " + feed.articles.length + ""); if (isSelected) { setBackground(table.getSelectionBackground()); }else{ setBackground(table.getSelectionForeground()); } } }
Now, our cell renderer becomes:
public class RssFeedCellRenderer implements TableCellRenderer{ RssFeedCellComponent feedComponent; public RssFeedCellRenderer() { feedComponent = new RssFeedCellComponent(); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { RssFeed feed = (RssFeed)value; feedComponent.updateData(feed, isSelected, table); return feedComponent; } }
And once again, nothing changed! But we did optimize (and that’s always good, right?). Now, there is only one instance of RssFeedCellComponent which changes it’s components depending on the RssFeed passed. In addition, creating a Cell Editor that returns
the same component is now pretty simple:
public class RssFeedCellEditor extends AbstractCellEditor implements TableCellEditor { RssFeedCellComponent feedComponent; public RssFeedCellEditor() { feedComponent = new RssFeedCellComponent(); } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { RssFeed feed = (RssFeed)value; feedComponent.updateData(feed, true, table); return feedComponent; } public Object getCellEditorValue() { return null; } }
And now we register our editor:
JTable table = new JTable(new RssFeedTableModel(feeds)); table.setDefaultRenderer(RssFeed.class, new RssFeedCellRenderer()); table.setDefaultEditor(RssFeed.class, new RssFeedCellEditor()); table.setRowHeight(60);
We can do even better. We can combine all three classes, RssFeedCellComponent, RssFeedCellRenderer and RssFeedCellEditor into a single RssFeedCell:
public class RssFeedCell extends AbstractCellEditor implements TableCellEditor, TableCellRenderer{ JPanel panel; JLabel text; JButton showButton; RssFeed feed; public RssFeedCell() { text = new JLabel(); showButton = new JButton("View Articles"); showButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { JOptionPane.showMessageDialog(null, "Reading " + feed.name); } }); panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); panel.add(text); panel.add(showButton); } private void updateData(RssFeed feed, boolean isSelected, JTable table) { this.feed = feed; text.setText("<strong>" + feed.name + "</strong>" + feed.url + "Articles " + feed.articles.length + ""); if (isSelected) { panel.setBackground(table.getSelectionBackground()); }else{ panel.setBackground(table.getSelectionForeground()); } } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { RssFeed feed = (RssFeed)value; updateData(feed, true, table); return panel; } public Object getCellEditorValue() { return null; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { RssFeed feed = (RssFeed)value; updateData(feed, isSelected, table); return panel; } }
Now we use only one Panel for all rendering and editing!
JTable with Renderer and Editor
The Conclusion
Every time I try to use a JTable I discover how difficult it is to just create a f*cking simple table, but I also see how powerful it is for more complex things. I’m pretty sure there is a way to display the button only on mouse over. You know, web2.0-y.Anyway, that’s that. As usual, you can find the source and the executable in myJava Corner.
Now excuse me, but I got some Void Rays to counter attack. En Taro Adun!
相关文章推荐
- How to use isInEditMode() to see layout with custom View in the editor
- ArrayIndexOutOfBoundsException with custom Android Adapter for multiple views in ListView
- iOS 运行出现:Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:], /Sourc
- Drop-in replacement for UINavigationController with custom transition animations
- How to create custom navigation menu in SharePoint with XML data source 使用XML数据源在SharePoint创建自定义导航菜单
- <ObjectC>解决 Assertion failure in -[UITableView _endCellAnimationsWithContext:] 问题
- How to create custom navigation menu in SharePoint with XML data source 使用XML数据源在SharePoint创建自定义导航菜单
- Building a ListBox with custom content in Silverlight 4.0
- Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:]
- How to Make Custom Drawn Gradient Backgrounds in a Grouped UITableView with Core Graphics
- Creating CustomBinding for WCFBasicHTTP for SSL and BasicAuthentication in BizTalk 转载自:http://geekswithblogs.net/mipsen
- Editing Null Data Values in a Cell with JavaFX 2
- How to Make Custom Drawn Gradient Backgrounds in a Grouped UITableView with Core Graphics
- How to integrate custom security policy with Windows domain authentication in ASP.NET
- *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit
- In-VitroCell ES NU-5810 Direct Heat 7 ft3 (200L) CO2 Incubator with Dual Sterilization Cycles
- Using Custom Domains With IIS Express In Asp.Net Core
- Custom an Site Hierachy with TreeNode in SharePoint 2010
- Create custom Task List and Forms in SharePoint 2010 with Visual Studio 2012
- Can I display a node in standard TTreeView component with bold style without custom drawing?