I want to filter a JTable exactly by a string. My filter is like this:
Pattern.quote(textfield.getText());
But, when I filter on "G" I get also all lines of the JTable with the entry "KG". I just want the rows with the entry "G". I looked at How to Use Tables: Sorting and Filtering, but I still don't see how.
As shown in How to Use Tables: Sorting and Filtering, use a RowFilter to "to filter out entries from the model so that they are not shown in the view." To match "G" exactly in column zero, your filter might look like this:
final RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>() {
#Override
public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
return "G".equals(entry.getStringValue(0));
}
};
Based on this example, pressing the button in the example below toggles the filter.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
/** https://stackoverflow.com/a/37114447/230513 */
public class JTableFilterDemo {
private static TableRowSorter<TableModel> sorter;
private Object[][] data = {{"G"}, {"KG"}, {"XG"}, {"Y"}, {"Z"}};
private String columnNames[] = {"Item"};
private TableModel model = new DefaultTableModel(data, columnNames) {
#Override
public Class<?> getColumnClass(int column) {
return String.class;
}
};
private JTable table = new JTable(model);
public JTableFilterDemo() {
table.setPreferredScrollableViewportSize(table.getPreferredSize());
sorter = new TableRowSorter<>(model);
table.setRowSorter(sorter);
final RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>() {
#Override
public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
return "G".equals(entry.getStringValue(0));
}
};
JScrollPane scrollPane = new JScrollPane(table);
JFrame frame = new JFrame("Filtering Table");
frame.add(new JButton(new AbstractAction("Toggle filter") {
#Override
public void actionPerformed(ActionEvent e) {
if (sorter.getRowFilter() != null) {
sorter.setRowFilter(null);
} else {
sorter.setRowFilter(filter);
}
}
}), BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JTableFilterDemo jtfd = new JTableFilterDemo();
}
});
}
}
Another example: RowFilter#regexFilter(...) (Java Platform SE 8)
The returned filter uses Matcher.find() to test for inclusion. To test for exact matches use the characters '^' and '$' to match the beginning and end of the string respectively. For example, "^foo$" includes only rows whose string is exactly "foo" and not, for example, "food". See Pattern for a complete description of the supported regular-expression constructs.
import java.awt.*;
import java.awt.event.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.table.*;
public class JTableFilterDemo2 {
public JComponent makeUI() {
String[] columnNames = {"Item"};
Object[][] data = {{"G"}, {"KG"}, {"XG"}, {"Y"}, {"Z"}, {"*G"}};
DefaultTableModel model = new DefaultTableModel(data, columnNames);
TableRowSorter<TableModel> sorter = new TableRowSorter<>(model);
JTable table = new JTable(model);
table.setRowSorter(sorter);
JTextField textField = new JTextField("G");
JButton button = new JButton("Toggle filter");
button.addActionListener(e -> {
if (sorter.getRowFilter() != null) {
sorter.setRowFilter(null);
} else {
String text = Pattern.quote(textField.getText());
String regex = String.format("^%s$", text);
sorter.setRowFilter(RowFilter.regexFilter(regex));
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(textField, BorderLayout.NORTH);
p.add(new JScrollPane(table));
p.add(button, BorderLayout.SOUTH);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new JTableFilterDemo2().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
How to update data to JTable from a vector after a button click? I have the code to add data to a JTable but it displays the same data for all the rows. Here's the following code:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import mygui.MainParent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Test {
Vector<String> row;
DefaultTableModel DFMO;
Vector<Vector> rowData;
JFrame frame;
private int count = 0;
public static void main(String arg[]){
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Test window = new Test();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Test() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton mybtn = new JButton("Click Me");
frame.getContentPane().add(mybtn, BorderLayout.SOUTH);
row = new Vector<String>();
rowData = new Vector<Vector>();
Vector<String> columnNames = new Vector<String>();
columnNames.addElement("Column One");
columnNames.addElement("Column Two");
columnNames.addElement("Column Three");
DFMO = new DefaultTableModel(rowData, columnNames);
JTable table = new JTable(DFMO);
JScrollPane scrollPane = new JScrollPane(table);
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.setSize(300, 150);
frame.setVisible(true);
mybtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
datachange();
}
});
datachange();
}
public void datachange(){
count++;
row.addElement("Row"+count+"-Column1");
row.addElement("Row"+count+"-Column2");
row.addElement("Row"+count+"-Column3");
rowData.addElement(row);
DFMO.fireTableDataChanged();
}
}
However, when I execute this code it doesn't show the updated row, even if the count value changes. The following image shows the output of the code.
Try this. You kept adding the same Vector
public void datachange(){
count++;
Vector<String> newRow = new Vector<>();
newRow.addElement("Row"+count+"-Column1");
newRow.addElement("Row"+count+"-Column2");
newRow.addElement("Row"+count+"-Column3");
DFMO.addRow(newRow);
//rowData.addElement(row);
DFMO.fireTableDataChanged();
}
You are adding the data to the Vector object (rowData).
What you should do is add the data directly to the DefaulTableModel object.
Replace
rowData.addElement(row);
with
DMFO.addRow(row);
in your datachange() method.
I have a problem creating a JTable in my GUI. The GUI is created in the main thread and enables a file to be opened. The file is then used to create a table model and add info to it. A JTable is then created with the table model and added to the GUI. My problem is that the GUI doesn't show. Code:
package example;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class Example extends JFrame{
private JButton button;
private JTable table;
private DefaultTableModel model;
private String path = "C:/Users/gilbert/Documents/11111.xls";
public Example(){
super("Example");
setLayout(new BorderLayout());
button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
createTableModel_Three_By_Six(path);
}
});
add(button,BorderLayout.NORTH);
setSize(400, 400);
}
public void createTableModel_Three_By_Six(String fpath){
model = new DefaultTableModel();
ExcelParser exPareser = new ExcelParser(fpath);
int rows = exPareser.getRowNumber();
String rowToAdd[] = new String[3];
int i, j = 0;
while(j < rows){
i= 0;
while(i < 3){
rowToAdd[i] = exPareser.accessRow(j);
i++;
j++;
if(j == rows){
if(i==1){
rowToAdd[1] = "";
rowToAdd[2] = "";
}
else if(i==2){
rowToAdd[2] = "";
}
}
}
model.addRow(rowToAdd);
}
table = new JTable(model);
add(new JScrollPane(table));
}
public static void main(String[] args) {
Example app = new Example();
app.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
app.setVisible(true);
}
}
The problem, with your example, is the fact that there are no columns for the table, which means when you add the table to the frame, it doesn't know how to display the table contents.
So, by doing something as simple as...
model = new DefaultTableModel(new Object[]{"A", "B", "C"}, 0);
//...
table = new JTable(model);
add(new JScrollPane(table));
revalidate();
I was able to get the table to appear properly, with it's contents
Beware though, each time you call this method, a new JTable will be created. Instead, you should construct the JScrollPane and JTable at an earlier stage and simply update the TableModel
I am working on a the GUI of a piece of code that I have been patching together. I am stuck at this part of the program where I would like a datafile the user chooses to be displayed in a JTable in a preview manner (i.e. the user should not be able to edit the data on the table).
With a button click from Experiment Parameters tab (see screenshot below), I create and run a "PreviewAction" which creates a new tab, and fills it up with the necessary components. Below is the code for DataPreviewAction. EDIT: I also posted a self-contained, minimal version of this that mimics the conditions in the real project, and exhibits the same behaviour.
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class MyFrame extends JFrame {
private JPanel panel1;
private JTabbedPane tabs;
private JButton runButton;
public MyFrame() {
tabs = new JTabbedPane();
panel1 = new JPanel();
runButton = new JButton("go!");
runButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
runButtonActionPerformed(evt);
}
});
panel1.add(runButton);
tabs.addTab("first tab", panel1);
this.add(tabs);
pack();
}
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager
.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
MyFrame frame = new MyFrame();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {
/*
* Normally there is more stuff happening here but this much will do for
* the sake of example
*/
List<String[]> data = new LinkedList<String[]>();
for (int i = 1; i < 1000; i++)
data.add(new String[] { "entry1", "value1", "value2", "value3" });
SwingUtilities.invokeLater(new DataPreviewAction(data, tabs));
}
public class DataPreviewAction implements Runnable {
private JTabbedPane contentHolder;
private List<String[]> data;
public DataPreviewAction(List<String[]> data, JTabbedPane comp) {
this.contentHolder = comp;
this.data = data;
}
#Override
public void run() {
DefaultTableModel previewModel = new DefaultTableModel() {
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
for (String[] datarow : data) {
previewModel.addRow(Arrays.copyOf(datarow, datarow.length,
Object[].class));
}
JTable table = new JTable(previewModel);
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton("A button"));
buttonPanel.add(new JLabel(
"Some description for the awesome table below "));
buttonPanel.add(new JButton("another button"));
JScrollPane tablePanel = new JScrollPane(table);
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(buttonPanel, BorderLayout.NORTH);
container.add(tablePanel, BorderLayout.CENTER);
contentHolder.addTab("Preview", container);
contentHolder.validate();
contentHolder.repaint();
}
}
}
There are at least two problems here:
The JTable (or the JScrollPane) does not render at all
The JScrollPane is not as wide as the frame itself, I have no idea why
I am not all that good in Swing so I might be missing something fundamental. I have checked that the datafile is read properly, and the data model contains the right amount of rows (1000+). SO the table should not be empty.
Suggestions?
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton("A button"));
buttonPanel.add(new JLabel("Some description for the awesome table below "));
buttonPanel.add(new JButton("another button"));
JScrollPane tablePanel = new JScrollPane(table);
JPanel container = new JPanel();
container.add(buttonPanel,BorderLayout.NORTH);
container.add(tablePanel,BorderLayout.SOUTH);
contentHolder.addTab("Preview", container);
//contentHolder.validate(); <- NO good
//contentHolder.repaint(); <- --"---
}
JPanel uses FlowLayout (implemented in API, acceptiong only PreferredSize, by default isn't resizable), correct output as is demonstrated in attn image, you have to change default LayoutManager for JPanel to BorderLayout, then code lines
.
container.add(buttonPanel,BorderLayout.NORTH);
container.add(tablePanel,BorderLayout.SOUTH);
will expands JComponents and can be works as you expecting, but I think tablePanel should be placed in CENTER area
EDIT:
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class MyFrame extends JFrame {
private JPanel panel1;
private JTabbedPane tabs;
private JButton runButton;
private JFrame frame = new JFrame();
private String[] columnNames = {"Nama", "Nim", "IP", "Hapus Baris ke"};
private Object[][] data = {
{"igor", "B01_125-358", "1.124.01.125", true},
{"lenka", "B21_002-242", "21.124.01.002", true},
{"peter", "B99_001-358", "99.124.01.001", false},
{"zuza", "B12_100-242", "12.124.01.100", true},
{"jozo", "BUS_011-358", "99.124.01.011", false},
{"nora", "B09_154-358", "9.124.01.154", false},
{"xantipa", "B01_001-358", "1.124.01.001", false},};
private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
#Override
public boolean isCellEditable(int row, int column) {
switch (column) {
case 3:
return true;
default:
return false;
}
}
#Override
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
public MyFrame() {
tabs = new JTabbedPane();
panel1 = new JPanel();
runButton = new JButton("go!");
runButton.addActionListener(new java.awt.event.ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
//
}
});
panel1.add(runButton);
tabs.addTab("first tab", panel1);
JTable table = new JTable(model);
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton("A button"));
buttonPanel.add(new JLabel("Some description for the awesome table below "));
buttonPanel.add(new JButton("another button"));
JScrollPane tablePanel = new JScrollPane(table);
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(buttonPanel, BorderLayout.NORTH);
container.add(tablePanel, BorderLayout.CENTER);
tabs.addTab("Preview", container);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tabs);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MyFrame frame = new MyFrame();
}
});
}
}
EDIT 2nd. e.g.
from code (included your idea about to fill data to model)
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class MyFrame extends JFrame {
private JPanel panel1;
private JTabbedPane tabs;
private JButton runButton;
private JFrame frame = new JFrame();
private String[] columnNames = {"Nama", "Nim", "IP", "Hapus Baris ke"};
private Object[][] data = {
{"igor", "B01_125-358", "1.124.01.125", "true"},
{"lenka", "B21_002-242", "21.124.01.002", "true"},
{"peter", "B99_001-358", "99.124.01.001", "false"},
{"zuza", "B12_100-242", "12.124.01.100", "true"},
{"jozo", "BUS_011-358", "99.124.01.011", "false"},
{"nora", "B09_154-358", "9.124.01.154", "false"},
{"xantipa", "B01_001-358", "1.124.01.001", "false"},};
private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
#Override
public boolean isCellEditable(int row, int column) {
switch (column) {
case 3:
return true;
default:
return false;
}
}
#Override
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
public MyFrame() {
tabs = new JTabbedPane();
panel1 = new JPanel();
runButton = new JButton("go!");
runButton.addActionListener(new java.awt.event.ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
List<String[]> data = new LinkedList<String[]>();
for (int i = 1; i < 10; i++) {
data.add(new String[]{"entry1", "value1", "value2", "value3"});
}
for (String[] datarow : data) {
model.addRow(Arrays.copyOf(datarow, datarow.length, Object[].class));
}
}
});
panel1.add(runButton);
tabs.addTab("first tab", panel1);
JTable table = new JTable(model);
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton("A button"));
buttonPanel.add(new JLabel("Some description for the awesome table below "));
buttonPanel.add(new JButton("another button"));
JScrollPane tablePanel = new JScrollPane(table);
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(buttonPanel, BorderLayout.NORTH);
container.add(tablePanel, BorderLayout.CENTER);
tabs.addTab("Preview", container);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tabs);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MyFrame frame = new MyFrame();
}
});
}
}
Following the footsteps of mKorbel I ended up doing some debugging. I am providing it here in case others run into the same problem.
It felt quite odd that the table looked OK when the underlying DataModel was supplied a data matrix upon initialisation
private DefaultTableModel model = new DefaultTableModel(data, columnNames)
but it would not show up properly when created with the empty constructor
private DefaultTableModel model = new DefaultTableModel()
and adding rows later with model.addRow(Object[] row);
I started look through the source code, and it turns out with the empty constructor the number of rows and columns for the model (private fields) is initiated to 0 and not updated properly afterwards. I noticed this while debugging since my tables had the dimension of 1370 x 0, which of course does not display properly.
Since I do not want to hardcode the number of rows/cols in advance the best course of action was to convert my "rows" to a matrix and provide the data to the model via constructor (much like mKorbel did). Here comes the fun part, if you want to supply the data then you need to supply the column names as well. THe fact that you have to have column names is counter-intuitive (IMHO), what happens if you dont have/need headers? The data is already in a table form, so I dont understand why column names is so important.
At any rate the following code renders the table at least:
String[] colNames = new String[data[1].length];
for(int i=0; i<colNames.length; i++)
colNames[i] = "C" + i;
DefaultTableModel model = new DefaultTableModel(data,colNames){
#Override
public boolean isCellEditable(int row, int column){
return false;
}};
I am accepting this because it points to the origin of the problem, but I would not be able to pinpoint the problem without mKorbel's answer, so give the upvote to his/her answer :)
I am trying to populate a table from a text file using vectors. Should I be creating a new vector for each row? Is there anything else that appears wrong with my code? I'm not quite sure what to do from here.
public class myJTable {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Vector<String> v = new Vector<String>();
Vector<Vector<String>> rowData = new Vector<Vector<String>>();
//String splitting
try {
FileReader fReader = new FileReader("lakedata.txt");
BufferedReader inFile = new BufferedReader(fReader);
String input;
String[] temp;
while((input=inFile.readLine())!=null) {
temp = input.split(",",3);
for(int i=0; i<temp.length; i++) {
v.addElement(temp[i]);
System.out.println(temp[i]);
}
System.out.println(v);
rowData.addElement(v);
}
inFile.close();
}
catch(Exception e) {
System.out.println("ERROR");
}
Vector<String> columnNames = new Vector<String>();
columnNames.addElement("Depth");
columnNames.addElement("Temperature");
columnNames.addElement("D.O.");
JTable table = new JTable(rowData, columnNames);
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(500,300);
frame.setVisible(true);
}
}
Instead of using Vector, you can create a DefaultTableModel then set it as the model of the table, then invoke its addRow method to directly put the read line result to the table. The table will automatically update itself when data to a table is added.
I took the liberty to revise your code into a cleaner one.
import java.awt.BorderLayout;
import java.io.BufferedReader;
import java.io.FileReader;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class TablePopulation {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
JTable table = new JTable();
JScrollPane scrollPane = new JScrollPane(table);
/* Set the table data to null first since
* we will fetch data from file line by line
* -------------------------------------- */
DefaultTableModel model = new DefaultTableModel(null, new String[]{"Depth","Temperature","D.O."});
table.setModel(model);
/* String splitting
* ---------------- */
try {
FileReader fReader = new FileReader("file.txt");
BufferedReader inFile = new BufferedReader(fReader);
String input = inFile.readLine();
while(input !=null) {
String[] temp = input.split(",");
model.addRow(temp);
input = inFile.readLine();
}
inFile.close();
}catch(Exception e) {
e.printStackTrace();
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(500,300);
frame.setVisible(true);
}
});
}
}
Also, atleast put e.printStackTrace() (since you're not using any logging framework) in the catch block to see the stack trace of what's going on in case some error has been caught.