Custom JPanel cell with JButtons in JTable
来源:互联网 发布:linux下编程工具 编辑:程序博客网 时间:2024/06/10 06:06
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 ;)
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 had 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!
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.
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!
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):
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!
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!
- Custom JPanel cell with JButtons in JTable
- Jtable加入jpanel
- custom cell accessoryView
- Custom Paging in ASP.NET 2.0 with SQL Server 2005
- android in practice_Working with a custom ContentProvider(MyMoviesContentProvider)
- How to sort an NSMutableArray with custom objects in it?
- Drop-in replacement for UINavigationController with custom transition animations
- ArrayIndexOutOfBoundsException with custom Android Adapter for multiple views in ListView
- (demo) : jtable的自定义cell
- Editing Null Data Values in a Cell with JavaFX 2
- Custom AntiAliasing with GDI+
- Custom DailyRollingFileAppender with MaxBackupIndex
- Exception handling with custom error pages in ASP.NET using C#.
- Can I display a node in standard TTreeView component with bold style without custom drawing?
- Develop Web Service With Axis2 #7 - Add Custom Soap Header in Response Soap Message
- How to Make Custom Drawn Gradient Backgrounds in a Grouped UITableView with Core Graphics
- Create custom Task List and Forms in SharePoint 2010 with Visual Studio 2012
- JSLink to custom list to render people column in detail with picture
- 刚开始学MFC,写点东西记录一下
- [msSql]字符串插入指定字符串函数
- 请用电驴下载--linux视频
- 为什么Eclipse替代不了Maven
- 解决Cannot set LC_CTYPE to default locale: No such file or directory
- Custom JPanel cell with JButtons in JTable
- 资源001(字符串,颜色,尺寸)
- BOJ 1003 Guess
- UML类图关系大全
- 打造你的范儿:雷锋网专访范儿街CEO李劲
- 网络存储NAS与SAN设备介绍以及区别
- Android JNI编程提高篇之一
- java存储图片到数据库
- Android JNI编程提高篇之二