Todos in Java Swing JTable - Java Source Code
Todo.java
package com.javaquizplayer.examples.todostable; /** * Represents a todo. * A todo has the attributes: * name - the description string. * isDone - a boolean indicating if the todo is completed or not. */ public class Todo { private String name; private boolean isDone; /* * Constructor builds a todo with default attribute values. */ public Todo() { this.name = "new todo"; this.isDone = false; } /* * Constructs a todo using the provided attribute values. */ public Todo(String name, boolean isDone) { this.name = name; this.isDone = isDone; } public void setName(String s) { name = s; } public String getName() { return name; } public void setIsDone(boolean b) { isDone = b; } public boolean getIsDone() { return isDone; } /* * The string representation of this todo. */ @Override public String toString() { return name + "|" + Boolean.toString(isDone); } }
MainWindow.java
package com.javaquizplayer.examples.todostable; import javax.swing.JFrame; import javax.swing.JTable; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.JOptionPane; import javax.swing.JSeparator; import javax.swing.ListSelectionModel; import javax.swing.event.TableModelEvent; import javax.swing.table.TableColumn; import javax.swing.border.LineBorder; import javax.swing.border.EmptyBorder; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import java.awt.FlowLayout; import java.awt.Dimension; import java.awt.Insets; import java.awt.Font; import java.awt.Color; import java.awt.event.KeyEvent; import java.util.List; import java.util.stream.Collectors; import java.io.IOException; /** * The main class that drives the application. * Displays the GUI - a JTable with the todo data and * allows a user to perform CRUD operations on the data. */ public class MainWindow { private static final String TITLE = "Todos"; private static FileUtils fileUtils; private JTable table; private JLabel message; private JTextField todoName; private TodoTableModel tmodel; // This is used to select the following row // when a todo is deleted (see tableModelListener()) private int selectedRowToDelete; /* * Main method, starts the application. * * Reads the todo data from the file and displays the * GUI with the todos. */ public static void main(String [] args) { MainWindow app = new MainWindow(); fileUtils = new FileUtils(); List<Todo> todos = null; try { todos = fileUtils.readAllTodosFromFile(); } catch(IOException e) { e.printStackTrace(); app.exitAppWithException(e.toString()); } app.displayGui(todos); } /* * Constructor. */ public MainWindow() { } /* * Constructs and displays the GUI for the main window of the application. * Displays the table with the input List<Todo> data. */ private void displayGui(List<Todo> todos) { // Table and model setup tmodel = new TodoTableModel(todos); tmodel.addTableModelListener(e -> tableModelListener(e)); table = new JTable(tmodel); // Table attributes setup table.setFillsViewportHeight(true); TableColumn isDoneColumn = table.getColumnModel().getColumn(1); isDoneColumn.setMaxWidth(100); table.setRowHeight(table.getRowHeight() + 20); Dimension dim = table.getIntercellSpacing(); table.setIntercellSpacing(new Dimension(dim.width+5, dim.height+5)); table.setGridColor(Color.LIGHT_GRAY); Font tableFont = table.getFont(); table.setFont(tableFont.deriveFont(4f + tableFont.getSize())); table.getTableHeader() .setFont(tableFont.deriveFont(Font.BOLD, 3f + tableFont.getSize())); // Table row selection and listener table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); ListSelectionModel selectionModel = table.getSelectionModel(); selectionModel.addListSelectionListener(e -> rowSelectionListener()); // Scroller for the table JScrollPane scrollPane = new JScrollPane(table); scrollPane.setBorder(BorderFactory.createCompoundBorder(new EmptyBorder(0, 0, 10, 0), LineBorder.createGrayLineBorder())); // Button panel JButton newButton = new JButton("New"); newButton.setMnemonic(KeyEvent.VK_N); newButton.addActionListener(e -> newButtonListener()); JButton saveButton = new JButton("Save"); saveButton.setMnemonic(KeyEvent.VK_S); saveButton.addActionListener(e -> saveButtonListener()); JButton deleteButton = new JButton("Delete"); deleteButton.setMnemonic(KeyEvent.VK_D); deleteButton.addActionListener(e -> deleteButtonListener()); JButton printButton = new JButton("Print"); printButton.setMnemonic(KeyEvent.VK_P); printButton.addActionListener(e -> printButtonListener()); JButton fileButton = new JButton("To file"); fileButton.setMnemonic(KeyEvent.VK_F); fileButton.addActionListener(e -> fileButtonListener()); JPanel buttonPanel = new JPanel(); // The layout allows centering the button panel // within mainPanel buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); buttonPanel.setBorder(new EmptyBorder(15, 0, 15, 0)); buttonPanel.add(newButton); buttonPanel.add(saveButton); buttonPanel.add(deleteButton); buttonPanel.add(printButton); buttonPanel.add(fileButton); // Input panel todoName = new JTextField(20); todoName.setMargin(new Insets(2, 2, 2, 2)); Font font = todoName.getFont(); todoName.setFont(font.deriveFont(4f + font.getSize())); // Message panel JPanel messagePanel = new JPanel(); messagePanel.setLayout(new FlowLayout(FlowLayout.LEFT)); message = new JLabel(""); messagePanel.add(message); message.setFont(new Font("Dialog", Font.PLAIN, 16)); // Main panel JPanel mainPanel = new JPanel(); mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); mainPanel.add(scrollPane); mainPanel.add(todoName); mainPanel.add(buttonPanel); mainPanel.add(new JSeparator()); mainPanel.add(messagePanel); // Finish frame and show JFrame frame = new JFrame(); frame.setTitle(TITLE); frame.add(mainPanel); frame.pack(); frame.setSize(new Dimension(400, 450)); // width x height frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); // centers the frame on screen frame.setVisible(true); // Initially, display this message int size = tmodel.getRowCount(); message.setText("There are " + Integer.toString(size) + " todos to work with!"); if (size > 0) { table.changeSelection(0, 0, false, false); // selects the first row } } /* * List selection listener. * This method runs after a row is selected in the table or * selection is changed in the tableModelListener() (due to * an insert or a delete). */ private void rowSelectionListener() { int viewRow = table.getSelectedRow(); if (viewRow >= 0) { int modelRow = table.convertRowIndexToModel(viewRow); // the todo name is populated in the name text field todoName.setText((String) tmodel.getValueAt(modelRow, 0)); } } /* * New button click event listener. * Creates a new todo, adds it to the table model and displays in the table. */ private void newButtonListener() { Todo todo = new Todo(); tmodel.addRow(todo); todoName.setText(todo.getName()); message.setText("New todo created."); todoName.requestFocusInWindow(); } /* * Save button click event listener. * Saves the modified todo's name to the table model. */ private void saveButtonListener() { int viewRow = table.getSelectedRow(); if (tmodel.getRowCount() == 0 || viewRow < 0) { return; } // Update the todo name with any changes - for the selected row int modelRow = table.convertRowIndexToModel(viewRow); String name = todoName.getText().trim(); if (name.isEmpty()) { // prompt the user to enter text for the name field (in case it is empty) JOptionPane.showMessageDialog(null, "Todo name cannot be empty!", TITLE, JOptionPane.INFORMATION_MESSAGE); todoName.requestFocusInWindow(); return; } boolean isDone = (Boolean) tmodel.getValueAt(modelRow, 1); tmodel.updateRow(modelRow, new Todo(name, isDone)); message.setText("Todo updated."); } /* * Delete button click event listener. * Deletes the selected todo from the table and the table model. */ private void deleteButtonListener() { selectedRowToDelete = table.getSelectedRow(); if (tmodel.getRowCount() == 0 || selectedRowToDelete < 0) { return; } int modelIx = table.convertRowIndexToModel(selectedRowToDelete); tmodel.deleteRow(modelIx); message.setText("Todo deleted."); } /* * Print button click event listener. * Prints the todos to the console. */ private void printButtonListener() { message.setText("Printing to console..."); System.out.println("# There are " + Integer.toString(tmodel.getRowCount()) + " todos #"); tmodel.getDataAsStream() .forEach(t -> System.out.format("%1$-7s : %2$s \n", (t.getIsDone() ? "Done" : "Pending"), t.getName())); System.out.println(); } /* * File button click event listener. * Gets all todos from the table model and writes them to a file. */ private void fileButtonListener() { List<String> todos = tmodel.getDataAsStream() .map(t -> t.getName() + "::" + Boolean.toString(t.getIsDone())) .collect(Collectors.toList()); try { fileUtils.writeToFile(todos); } catch(IOException e) { e.printStackTrace(); exitAppWithException(e.toString()); } message.setText(Integer.toString(tmodel.getRowCount()) + " todos saved to file: " + fileUtils.getFileName()); } /* * Routine to exit the application in case of an exception with * the file read and write operations. The exception message is * displayed in a dialog, and exit the program. */ private void exitAppWithException(String exceptionMessage) { JOptionPane.showMessageDialog(null, exceptionMessage, "Todos file error", JOptionPane.ERROR_MESSAGE); System.exit(0); } /* * Table model listener. * This listens for changes (insert and delete) to the table model. * The routine allows the application to position the selected or new * row during model changes. * Also, see rowSelectionListener(), TodoTableModel.java * NOTE: TableModelEvent Type: 1=insert, 0=update and -1=delete */ private void tableModelListener(TableModelEvent e) { int modelRow = e.getFirstRow(); // Selects the same table row with the next todo if there are rows // following the deleted todo. Otherwise, selects the previous row. if (e.getType() == TableModelEvent.DELETE) { if (tmodel.getRowCount() == 0) { // all todos are deleted, clear the text field todoName.setText(""); return; } if (tmodel.getRowCount() <= selectedRowToDelete) { --selectedRowToDelete; // get previous row for selection } // Change table row selection to the same row or previous row table.changeSelection(selectedRowToDelete, 0, false, false); } // Selects the newly inserted row. if (e.getType() == TableModelEvent.INSERT) { int insertRow = table.convertRowIndexToView(modelRow); table.clearSelection(); table.changeSelection(insertRow, 0, false, false); } } }
TodoTableModel.java
package com.javaquizplayer.examples.todostable; import java.util.List; import java.util.stream.Stream; import javax.swing.table.AbstractTableModel; /** * Table model with todos data for the JTable. * Overrides several methods of the AbstractTableModel abstract * class for managing the table's data model. */ public class TodoTableModel extends AbstractTableModel { private List<Todo> data; // the model data displayed in the JTable private final String [] columnNames = { "Description", "Done" }; /* * Constructor. * Initializes the model's data with the input List<Todo>. */ public TodoTableModel(List<Todo> input) { this.data = input; } @Override public int getRowCount() { return data.size(); } @Override public int getColumnCount() { return columnNames.length; } @Override public Object getValueAt(int rowIx, int colIx) { Todo todo = data.get(rowIx); switch(colIx) { case 0: return todo.getName(); case 1: return todo.getIsDone(); } return null; } /* * This method is implemented to make the table cells editable. * Also, see isCellEditable() */ @Override public void setValueAt(Object value, int rowIx, int colIx) { Todo todo = data.get(rowIx); switch(colIx) { case 0: todo.setName((String) value); break; case 1: todo.setIsDone((Boolean) value); break; } } @Override public String getColumnName(int colIx) { return columnNames[colIx]; } @Override public boolean isCellEditable(int rowIx, int colIx) { if (colIx == 1) { // specifies that the 2nd column isDone can be edited, ie., // the check box can be checked or unchecked in the JTable return true; } return false; } @Override public Class getColumnClass(int colIx) { return getValueAt(0, colIx).getClass(); } /* * Returns the table model data as a stream. * Useful for writing the data to the file or printing to the console. */ public Stream<Todo> getDataAsStream() { return data.stream(); } /* * Adds a newly created todo to the model and * triggers the model listener. The new todo is * appended to the end of the model. */ public void addRow(Todo newTodo) { data.add(newTodo); fireTableRowsInserted((getRowCount() - 1), (getRowCount() - 1)); } /* * Updates the modified todo to the model and * triggers the model listener. */ public void updateRow(int modelRowIx, Todo modified) { data.set(modelRowIx, modified); fireTableRowsUpdated(modelRowIx, modelRowIx); } /* * Deletes the selected todo from the model and * triggers the model listener. */ public void deleteRow(int modelRowIx) { data.remove(modelRowIx); fireTableRowsDeleted(modelRowIx, modelRowIx); } }
FileUtils.java
package com.javaquizplayer.examples.todostable; import java.io.IOException; import java.nio.file.Paths; import java.nio.file.Path; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.ArrayList; import java.util.stream.Collectors; /** * Utility class with methods to read and write todos data file. */ public class FileUtils { private static final String FILE_NAME = "todos.txt"; private final Path filePath; /* * Constructs an instance. * Creates the filePath object for the todos file. */ public FileUtils() { filePath = Paths.get(FILE_NAME); } public String getFileName() { return FILE_NAME; } /* * Reads the todo data stored in the file and returns * them as List<Todo> collection. */ public List<Todo> readAllTodosFromFile() throws IOException { List<Todo> todos = new ArrayList<>(); // Checks if the todos file exists. // Reads from the file or creates a new file (if not exists). if (Files.exists(filePath)) { List<String> data = Files.readAllLines(filePath); todos = data.stream() .map(s -> s.split("::")) .map(ss -> new Todo(ss[0], Boolean.valueOf(ss[1]))) .collect(Collectors.toList()); } else { Files.createFile(filePath); } return todos; } /* * Writes the input List<String> with todos (formatted as strings) to the file. */ public void writeToFile(List<String> todos) throws IOException { Files.write(filePath, todos, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } }
Return to top
This page uses Java code formatting from http://hilite.me/.