Java Quiz Player

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/.