JTable, 3dimensional matrix and multiple panels - java

I am trying to fit data from a three-dimensional matrix into multiple JTables. I am using a layout that consists of multiple panels which are associated in a manner that gives me the layout I target (I'm not too familiar with GridBagLayout, so I'm putting my own layout together).
The problem is now that the tables are not displayed on the Frame, and I don't know if the problem are the multiple panels or if it's because I'm using a 3-dimensional matrix.
Using JTable works when not using fields of tables, layers, etc. - I assume sth. must go wrong there.
I'm very grateful for your help and tipps! Thanks a lot!
Here's my code:
import java.awt.*;
import javax.swing.*;
import javax.swing.JTable;
public class Tabellen extends JFrame{
private static final long serialVersionUID = 7526472295622776147L;
Container c;
JPanel p_tabellen;
JPanel[] p_tab;
JTable[] table;
String[] columnnames={};
String[][][] matrixStr;
double[][][] matrix;
public Tabellen(double [][][] matrix) {
//create a matrix of Strings from a double-matrix that can be read by the JTable constructor
this.matrix=matrix;
matrixStr = new String[matrix.length][matrix[0].length][matrix[0][0].length];
for (int dim=0; dim<matrix.length; dim++){
for (int zeile=0; zeile<matrix[0].length; zeile++){
for (int spalte=0; spalte<matrix[0][0].length; spalte++){
matrixStr[dim][zeile][spalte]= String.valueOf(matrix[dim][zeile][spalte]);
}
}
}
//create panels and Layouts
c = getContentPane();
p_tabellen= new JPanel(new GridLayout(matrix.length,1));
p_tab= new JPanel[matrix.length];
for (int p=0; p<matrix.length; p++){
p_tab[p]= new JPanel(new BorderLayout());
p_tabellen.add(p_tab[p]);
}
c.add(p_tabellen);
//create one table per panel
table = new JTable[matrix.length];
for (int dim=0; dim<matrix.length; dim++){
for (int zeile=0; zeile<matrix[0].length; zeile++){
for (int spalte=0; spalte<matrix[0][0].length; spalte++){
table[dim]= new JTable(matrixStr[dim],columnnames);
p_tab[dim].add(table[dim], BorderLayout.CENTER);
}
}
}
}
}
Input example:
public class TEST {
public static void main(String[] args) {
double [][][] matrix = {{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}}};
Tabellen d= new Tabellen(matrix);
d.setTitle("test");
d.setSize(1300,720);
d.setVisible(true);
d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

Assuming each of dim tables is meant to display zeile rows in spalte columns, create a List<TableModel> having dim entries, one for each table. Create a single JTable and update its model using setModel(). Let the user select the currently displayed model using an adjacent control. This example uses a JComboBox, but JSpinner is a good alternative. More on creating a TableModel may be found here.

Related

Editing JTable cells after being cloned

I'm working on a project for work and I have reached an unusual problem. I have a JTable that the user can populate with data. In my actual code there is an "add row" button that lets the user fill out some GUI's and that information generates the row.
Another important feature of this is a button to clone rows. Since the process of adding a row can be very time consuming (there are many fields to fill out) if a user only needs to add a new row with 1 cell different then he can clone the cell using the button.
This clone button works as expected however there is a rather odd problem. Once a row has been cloned I noticed that when I attempt to change the contents of any cells that have been cloned there are unexpected results. For example if I change a cell's contents to "Ryan" then other cells may also suddenly change and if I even click on a cell after changing one the cell I click on will change by itself. I'm quite sure that this problem is related to the clone method I just really have no idea to fix.
I created a verifiable program so you can text it out for yourself and see what I'm talking about. Just use the clone button a few times and then try changing the contents of individual cells and watch the results in the other cells..
I really need to fix this but I'm lost on what to do, and help is GREATLY appreciated.
Main Class
package jtabletest;
public class JTableTestMain
{
public static void main(String args[]){
JTableTest jTest = new JTableTest();
jTest.createGUI();
}
}
JTable Class
package jtabletest;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class JTableTest
{
protected static DefaultTableModel dtm = new DefaultTableModel();
public static JTable tbl;
public void createGUI(){
final JFrame frame = new JFrame("JTable Test");
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel panelNorth = new JPanel(new BorderLayout());
JPanel panelSouth = new JPanel(new BorderLayout());
JPanel buttonPanel = new JPanel();
JButton cloneButton = new JButton("Clone");
cloneButton.setPreferredSize(new Dimension(150,40));
buttonPanel.add(cloneButton);
JButton printButton = new JButton("Print");
printButton.setPreferredSize(new Dimension(150,40));
buttonPanel.add(printButton);
tbl = new JTable();
String header[] = new String[]{
"Employee", "Pay-Rate", "Hours Worked"};
dtm.setColumnIdentifiers(header);
tbl.setModel(dtm);
tbl.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for(int i = 0; i < header.length; i++){
tbl.getColumnModel().getColumn(i).setPreferredWidth(200);
}
dtm.addRow(new Object[]{"Pete","$10.00","40"});
dtm.addRow(new Object[]{"Bob","12.50","42"});
dtm.addRow(new Object[]{"Jamar","$7.25,25"});
cloneButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
int[] selectedRows = tbl.getSelectedRows();
if(selectedRows.length>0){
#SuppressWarnings("rawtypes")
Vector data = dtm.getDataVector();
int insertPoint = selectedRows[selectedRows.length-1]+1;
for(int i = 0; i < selectedRows.length; i++){
#SuppressWarnings("rawtypes")
Vector targetRow = (Vector)data.elementAt(selectedRows[i]);
dtm.insertRow(insertPoint, targetRow);
insertPoint++;
}
dtm.fireTableDataChanged();
}
}
});
printButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
if(null != tbl.getCellEditor()){
tbl.getCellEditor().stopCellEditing();
}
for(int i = 0; i < tbl.getRowCount(); i++){
System.out.println(tbl.getValueAt(i, 0));
System.out.println(tbl.getValueAt(i, 1));
System.out.println(tbl.getValueAt(i, 2));
}
}
});
panelNorth.add(tbl,BorderLayout.NORTH);
panelNorth.setPreferredSize(new Dimension(500,500));
panelSouth.add(buttonPanel,BorderLayout.NORTH);
mainPanel.add(panelNorth,BorderLayout.NORTH);
mainPanel.add(panelSouth,BorderLayout.SOUTH);
frame.add(mainPanel);
frame.setVisible(true);
frame.setSize(1900,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
It sounds like you are reusing the same reference rather than copying new objects on the clone method. I would suggest doing the following.
1) First create a new Vector and see if that will do the trick, like so.
for(int i = 0; i < selectedRows.length; i++){
#SuppressWarnings("rawtypes")
Vector targetRow = (Vector)data.elementAt(selectedRows[i]);
Vector newVector = new Vector();
for (int t = 0; t < targetRow.size(); t++) {
newVector.add(targetRow.get(t));
}
dtm.insertRow(insertPoint, newVector);
insertPoint++;
}
and see if this will resolve your problem. If it does you are done. If it doesn't then
2) Create a new Vector similar to above, for any Class based object in the Vector recreate them as currently you are dealing with pointers.
It's a bit hard for me to say if #1 will fix your problem as I don't know the contents of the Vector coming from the table, if it is primitives you are probably safe otherwise you may need to do solution #2.
Your problem is in this line:
Vector targetRow = (Vector)data.elementAt(selectedRows[i]);
you are not creating a copy, you are creating a new reference so when you add
dtm.insertRow(insertPoint, targetRow)
the row you are adding is actually the same, not a copy of the previosly selected row.
You will have to use something like
Vector aux = (Vector)data.elementAt(selectedRows[i]);
Vector targetRow = aux.clone();
to make it work.
Clone is the keyword here. You are not cloning the data. You are just copying the references from one Vector to another. So since each row shares the same references the value appears in both rows.
So you need to actually clone each element.
The code would be something like:
Vector targetRow = (Vector)data.elementAt(selectedRows[i]);
Vector clonedRow = new Vector(targetRow.size());
for (Object object: targetRow)
{
clonedRow.addElement( object.clone() );
}
Note, I've never used clone() before so you might be able to use:
Vector targetRow = (Vector)data.elementAt(selectedRows[i]);
Vector clonedRow = targetRow.clone();
but I'm not sure if it just clones the Vector and not the elements in the Vector.
Also, you would never invoke the firstTableDataChanged() method. That is the job of the DefaultTableModle to fire the appropriate method when the insertRow(...) method is invoked.
Edit:
Yes, using the clone does work but you need to clone the Vector not each item in the Vector:
//dtm.insertRow(insertPoint, targetRow);
dtm.insertRow(insertPoint, (Vector)targetRow.clone());
or
dtm.insertRow(insertPoint, new Vector(targetRow));

How can I display a regular grid quickly?

I want to display a grid of 400 identically-sized JPanels. The usual approach seems to be to create and lay out all the panels, and then actually display them. In my application, however, most of the panels actually start out hidden (think "minesweeper", but with much more complicated panels), so I'd love to be able to display an "empty" grid, and then add the panels to it as I need them. Two approaches I've considered:
Dispense with a layout manager and simply add panels at the appropriate absolute coordinates as necessary.
Use a layout manager, but start off filling up the table with dummy components and replace them with the complicated ones as I go.
Using either of these approaches, however, I seem to need to know the panel size in advance, which I don't. I could fix this by building a sample panel and measuring it, but that seems rather ugly, and duplicates a bunch of code. Is there some other way to do this?
Use the flyweight pattern to render only visible panels. The approach is illustrated in JTable renderers and outlined here.
I would not use panels or custom painting here. Instead:
Component: JToggleButton
Layout: GridLayout
Tiles: Icon (standard, focused, pressed, selected etc.)
E.G.
import java.awt.*;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class MineSweeper {
public static final int COLS = 20;
public static final int ROWS = 20;
public static void main(String[] args) throws Exception {
URL urlDefault = new URL("http://i.stack.imgur.com/in9g1.png");
URL urlPressed = new URL("http://i.stack.imgur.com/1lgtq.png");
URL urlSelected = new URL("http://i.stack.imgur.com/wCF8S.png");
final Image imgDefault = ImageIO.read(urlDefault);
final Image imgPressed = ImageIO.read(urlPressed);
final Image imgSelected = ImageIO.read(urlSelected);
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridLayout(ROWS, COLS, 2, 2));
ImageIcon iiDefault = new ImageIcon(imgDefault);
for (int ii = 0; ii < COLS; ii++) {
for (int jj = 0; jj < ROWS; jj++) {
JToggleButton tb = new JToggleButton(iiDefault);
tb.setContentAreaFilled(false);
tb.setMargin(new Insets(0,0,0,0));
tb.setPressedIcon(new ImageIcon(imgPressed));
tb.setSelectedIcon(new ImageIcon(imgSelected));
gui.add(tb);
}
}
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}

JLabels to be resized in a GridLayout

I have a very simple Java program (see below). The GridLayout has 20 rows and 4 columns. As you know the elements are supposed to be added horizontally by (GridLayout) definition. However, I get the two elements (labels) placed one above the other, vertically.
I colored them and realised the labels take up the whole row, hence the vertical effect. But then I also used setSize(5,5) with each to make them smaller, however they still take up the whole row. Any advice as to why this happens and how to fix/set smaller size/etc?
public class Sam extends JFrame {
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[20];
public Sam()
{
c = getContentPane();
c.setLayout(new GridLayout(20,4));
lbl[1] = new JLabel("Column1");
c.add(lbl[1]);
lbl[2] = new JLabel("Column2");
c.add(lbl[2]);
show();
}
public static void main(String[] args)
{
Sam x = new Sam();
x.setVisible(true);
x.setSize(7500,4500);
}
}
You're only adding two components to the grid so they will fill it up. You need to add more components to the grid as placeholders so that it can place the original JLabels in their proper place, perhaps empty JLabels or JPanels.
As an aside, you should avoid setting the size of any Swing component. Your current size of 7500, 4500 is a bit on the large size.
As a second aside, perhaps you want to use a JTable instead here.
Edit: if you want a GridLayout with 4 columns and variable number of rows, use 0 for your GridLayout row constant:
c.setLayout(new GridLayout(0, 4));
e.g.,
import java.awt.*;
import javax.swing.*;
public class Sam extends JFrame {
public static final int COLUMN_COUNT = 4;
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[COLUMN_COUNT];
public Sam() {
c = getContentPane();
c.setLayout(new GridLayout(0, COLUMN_COUNT));
for (int i = 0; i < lbl.length; i++) {
lbl[i] = new JLabel("Column " + (i + 1));
c.add(lbl[i]);
}
}
public static void main(String[] args) {
Sam x = new Sam();
x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x.pack();
x.setLocationRelativeTo(null);
x.setVisible(true);
// x.setSize(7500,4500);
}
}
But still I wonder if a JTable wouldn't work better here.
One thing to keep in mind with the GridLayout is it that it is designed to cover the entire containing panel sizing the cells as equally as possible, and elements added to the cells will be expanded to fill the entire cell. So as the cell sizes change, the labels will also change in size. Effectively grid cells force an expansion/contraction in both X and Y direction of all contained elements.
One way to prevent that from happening if you must use the GridLayout is to not add the labels directly to the container that uses the GridLayout, but instead put each label inside a JPanel that uses a FlowLayout (the default) that you can set alignment of either Left, Middle or Right, then add that JPanel to the Grid container. The JPanel will be resized but it will not change the size of the Label.
Or use the GridBagLayout manager. More complex, but once you understand it, it makes life easier. But as Hovercraft mentioned, if what you are trying to do is create a grid with column headers, a JTable might be a better option.

Java: forcing a component to fill up the entire row in a GridLayout

I am writing a program that allows multiple users the share screenshots. Every time a user connects, everyone who is participating in the "room" (a bunch of users that are able to receive screen shots from one another) becomes able to see a screen shot that the user takes. To be able to see the screen shot, the frame needs to split itself up so that there is a dedicated space for that user's screen shots.
I decided to use a GridLayout because it splits components into equally-sized rectangles which is what I am looking for. The layout does exactly what I need it to, except there is one problem. If I my GridLayout configured that there are two rows and columns, the bottom-most row will still be split into two columns, even when there is only a single component. This is expected behavior, but is there a walk-around, preferably without using a different layout? I really like the simplicity of GridLayout. I have considered using a BorderLayout, but it is limited because there is a set amount of spaces where I can place items.
The format of the pictures wasn't supported, so I could not embed them into this question.
Here is how the frame looks like it is full. I substituted the actual screen shots for buttons because I am just testing.
http://cl.ly/0N311g3w061P1B0W1T3s/Screen%20shot%202012-05-13%20at%204.23.25%20PM.png
Now here is how it looks when I remove a button from the bottom-most row:
http://cl.ly/2j3Z0V1r3w1S3F160j05/Screen%20shot%202012-05-13%20at%204.23.41%20PM.png
Here is how I would want the bottom-most row to look:
http://cl.ly/0J2R2y2L06151F0k0Y0i/Screen%20shot%202012-05-13%20at%204.24.11%20PM.png
How can I make the bottom-most row look like that? Keep in mind I still want the other rows to have two columns, but I only want the bottom-most one to have one column.
Thanks!
To my knowledge, you can't. GridLayout is done this way.
But GridBagLayout will do a beautiful job for your program.
Take a look at this small demo that lays out buttons in rows and columns.
(Click on a button to remove it).
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test4 {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
final JPanel root = new JPanel(new GridBagLayout());
frame.add(root);
frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer t = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final JButton b = new JButton("Hello" + root.getComponentCount());
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
root.remove(b);
updateConstraints(root);
}
});
root.add(b);
updateConstraints(root);
}
});
t.start();
}
});
}
protected static void updateConstraints(JPanel root) {
if (!(root.getLayout() instanceof GridBagLayout)) {
System.err.println("No a gridbaglayout");
return;
}
GridBagLayout layout = (GridBagLayout) root.getLayout();
int count = root.getComponentCount();
int col = (int) Math.round(Math.sqrt(count));
int row = (int) Math.ceil((double) count / col);
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
int index = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
gbc.gridx = j;
gbc.gridy = i;
boolean last = index + 1 == count;
if (last) {
gbc.gridwidth = col - j;
}
Component c = root.getComponent(index);
layout.setConstraints(c, gbc);
if (last) {
break;
}
index++;
}
}
root.doLayout();
}
}
I decided to go with a slightly different approach. Since the separate screens are laid out really nicely using GridLayout when there are an even amount of screens, I decided to simply split up the screens into pages if there is an odd amount of screens.
I think you want to use the GridBagLayout - check out the visual guide to layouts
In particular, with a GridBagLayout, you add components with a GridBagConstraints. This allows you to specify where each component should be put, but also what weight each component should have - e.g. see the GridBagLayout tutorial.

Runtime alignment of JComponents + chaining to RowFilters

I'm currently working on a rather complex application. My job is to build parts of the GUI.
The main area is derived for JTable and contains all application relevant data. There are a few elements on top of the Table, that allow the user to control the way the data is shown in the table.
The options relevant to the task at hand are:
Changing of number of columns,
Independently changing of width of columns (not by means of JTableHeader) and
Entering one filter term per column to select specific rows of the data.
The main goal in this szenario is to create a Component (probably JTextField) for every column in the current viewsetting, which is accuratly aligned with that column (although it changes size at runtime).
First question:
The alignment doesn't work. I can't get the width of the TextFields to match the width of the columns.
How do i get it to work?
Second problem:
I want the individual filters to be chained. That is, if the user decides to enter more then one filter string, all of them should be evaluated for their respective columns and only the rows that match all filters should be shown. So far the input in a second TextField delets the first filter (which is working decently using RowFilter.regexFilter).
How do i get this to work?
Please let me know, which code snippets could be useful to you and i will be glad to post them.
Thanks in advance for any help given.
Regards, DK
I can't get the width of the
TextFields to match the width of the
columns
This example should get you started:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class TableFilterRow extends JFrame implements TableColumnModelListener
{
private JTable table;
private JPanel filterRow;
public TableFilterRow()
{
table = new JTable(3, 5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
table.getColumnModel().addColumnModelListener( this );
// Panel for text fields
filterRow = new JPanel( new FlowLayout(FlowLayout.CENTER, 0 , 0) );
for (int i = 0; i < table.getColumnCount(); i ++)
filterRow.add( new JTextField("" + i) );
columnMarginChanged( new ChangeEvent(table.getColumnModel()) );
getContentPane().add(filterRow, BorderLayout.NORTH);
}
// Implement TableColumnModelListener methods
// (Note: instead of implementing a listener you should be able to
// override the columnMarginChanged and columMoved methods of JTable)
public void columnMarginChanged(ChangeEvent e)
{
TableColumnModel tcm = table.getColumnModel();
int columns = tcm.getColumnCount();
for (int i = 0; i < columns; i ++)
{
JTextField textField = (JTextField)filterRow.getComponent( i );
Dimension d = textField.getPreferredSize();
d.width = tcm.getColumn(i).getWidth();
textField.setPreferredSize( d );
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
filterRow.revalidate();
}
});
}
public void columnMoved(TableColumnModelEvent e)
{
Component moved = filterRow.getComponent(e.getFromIndex());
filterRow.remove(e.getFromIndex());
filterRow.add(moved, e.getToIndex());
filterRow.validate();
}
public void columnAdded(TableColumnModelEvent e) {}
public void columnRemoved(TableColumnModelEvent e) {}
public void columnSelectionChanged(ListSelectionEvent e) {}
public static void main(String[] args)
{
JFrame frame = new TableFilterRow();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
only the rows that match all filters
should be shown
Read the JTable API and follow the link to the Swing tutorial on "How to Use Tables" where you will find the TableFilterDemo. You can easily modify the code to use "and" filters. The code would be something like:
// rf = RowFilter.regexFilter(filterText.getText(), 0);
List<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2);
filters.add(RowFilter.regexFilter(filterText.getText(), 0));
filters.add(RowFilter.regexFilter(filterText.getText(), 1));
rf = RowFilter.andFilter(filters);
This examples shares a single text field looking for the same string in multiple columns. You would obviously use your individual text fields.

Categories

Resources