I am making a GUI utility that will allow to create a question paper through it. A question can have multiple parts. I have made a basic wireframe for this:
My problem is that I can easily make text fields for questions and adding further questions, but how will I associate the parts of a question to it? Basically I have a JSON array in the back end where a question will be stored along with its parts.
QuestionModel represents question-data where all strings forming a question are stored in a collection.
QuestionView represents a view of a single question.
Questionnaire acts as a controller which also creates a dynamic view of QuestionViews.
For convenience the entire code can be copied into Questionnaire.java and run:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.TextField;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
//Questionnaire acts as a controller which also creates a dynamic view of QuestionViews.
public class Questionnaire implements ChangeListener{
private final List<QuestionView> questions;
private final List<QuestionModel> questionModels;
private JPanel questionPanel;
private JFrame frame;
Questionnaire() {
questions = new ArrayList<>();
questionModels = new ArrayList<>();
createAndShowGui();
}
private void createAndShowGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
questionPanel = new JPanel();
BoxLayout layout = new BoxLayout(questionPanel, BoxLayout.Y_AXIS);
questionPanel.setLayout(layout);
addQuestion();
JButton addQuestionButton = new JButton("Add Question");
addQuestionButton.addActionListener(e->addQuestion());
JButton submitButton = new JButton("Submit");
submitButton.addActionListener(e->collectQuestion());
JPanel buttonsPanel = new JPanel(new BorderLayout(10,10));
buttonsPanel.add(addQuestionButton, BorderLayout.WEST);
buttonsPanel.add(submitButton, BorderLayout.EAST);
JPanel mainPane = new JPanel(new BorderLayout(10,10));
mainPane.add(questionPanel,BorderLayout.CENTER);
mainPane.add(buttonsPanel,BorderLayout.SOUTH);
frame.add(mainPane);
frame.pack();
frame.setVisible(true);
}
//adds a question to the questionnaire
private void addQuestion() {
QuestionModel model = new QuestionModel();
QuestionView view = new QuestionView(model);
view.setListener(this);
questions.add(view);
questionModels.add(model);
questionPanel.add(view.getView());
refresh();
}
//refresh view whan something changed
void refresh(){
frame.pack();
}
// ChangeListener implementation
#Override
public void changed() {
refresh();
}
//collect all strings from text fields and update models
private void collectQuestion() {
for(QuestionView view : questions){
view.collectQuestions();
}
//for testing
printQuestions();
}
void printQuestions(){
for(QuestionModel model : questionModels){
System.out.println(model.getQuestion());
}
}
public static void main(String[] args) {
new Questionnaire();
}
}
//QuestionModel represents question-data where all strings forming a question are stored in a collection.
class QuestionModel {
private final List<String> questionParts;
QuestionModel() {
questionParts = new ArrayList<>();
}
void addQuestionPart(String text){
questionParts.add(text);
}
List<String> getQuestionParts() {
return questionParts;
}
//return the whole question as one string
String getQuestion(){
StringBuilder sb = new StringBuilder();
for(String part : questionParts){
sb.append(part).append(" ");
}
return sb.toString();
}
}
//QuestionView represents a view of a single question
class QuestionView {
private final List<TextField> questionPartsTf;
private final JPanel tfPanel, mainPanel;
private ChangeListener listener; //used to notify that view changed
private final QuestionModel model;
QuestionView(QuestionModel model) {
this.model = model;
questionPartsTf = new ArrayList<>();
tfPanel = new JPanel(new GridLayout(1, 0, 0, 10));
JLabel addLabel = new JLabel("Add question: ");
tfPanel.add(addLabel);
addTextField();
JButton addButton = new JButton("Add question part");
addButton.addActionListener(e->addTextField());
mainPanel = new JPanel(new BorderLayout(10,10));
mainPanel.add(tfPanel, BorderLayout.WEST);
mainPanel.add(addButton, BorderLayout.AFTER_LINE_ENDS);
}
private void addTextField() {
TextField tf= new TextField(15);
tfPanel.add(tf);
questionPartsTf.add(tf);
if(listener != null){
listener.changed();
}
}
//collect all strings from text fields and update model
void collectQuestions(){
for(TextField tf : questionPartsTf){
if(!tf.getText().isEmpty()){
model.addQuestionPart(tf.getText());
}
}
}
JComponent getView(){
return mainPanel;
}
void setListener(ChangeListener listener) {
this.listener = listener;
}
}
//interface for listening to view changes
interface ChangeListener{
void changed();
}
Run it on line: https://repl.it/repls/VastNaiveModules
If I understand your question correctly, you want to associate parts with questions. Here's a plain Java class that holds one question and as many parts as needed.
import java.util.ArrayList;
import java.util.List;
public class Question {
private String question;
private List<String> parts;
public Question(String question) {
this.question = question;
this.parts = new ArrayList<>();
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public void addPart(String part) {
this.parts.add(part);
}
public List<String> getParts() {
return parts;
}
}
You would keep a List of Question instances in another class, your application model class.
As far as the view, you would create an additional JTextField each time the user clicks on the add parts JButton. You associate those JTextFields with the parts in the Question class.
Related
I have a problem with updating a textArea from another class.
I need a textArea to show a text while pressing a button.
So when I press a buton I make a method actionPerformed() in ParceListener to print a text in a textArea which is located in MainFormAppearance class. But it doesn't do that. Could you please help me?
public class Main {
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame jFrame = new JFrame("Title");
MainFormAppearance demo = new MainFormAppearance();
jFrame.setContentPane(demo.createContentPanel());
jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE);
jFrame.setSize(400,300);
jFrame.setVisible(true);
}
}
MainFormAppearance
package com.company;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFormAppearance {
public JPanel totalGui;
public JTextArea frame;
public JLabel blueLabel;
public JButton parceButton;
public JButton mailButton;
public ParceListener parceListener;
public JPanel createContentPanel(){
totalGui = new JPanel();
frame = new JTextArea();
blueLabel = new JLabel("Some program");
parceButton = new JButton("Button 1");
mailButton = new JButton("Button 2");
parceListener = new ParceListener();
totalGui.setLayout(null);
//set program window
blueLabel.setLocation(10,10);
blueLabel.setSize(400,20);
blueLabel.setHorizontalAlignment(SwingConstants.CENTER);
blueLabel.setForeground(Color.blue);
totalGui.add(blueLabel);
//set Button 1
parceButton.setLocation(270, 50);
parceButton.setSize(100,30);
totalGui.add(parceButton);
//Pressing the Button 1
parceButton.addActionListener(parceListener);
//set Button 2
mailButton.setLocation(270, 100);
mailButton.setSize(100, 30);
totalGui.add(mailButton);
frame.setLocation(20, 115);
frame.setSize(200, 15);
totalGui.add(frame);
totalGui.setOpaque(true);
return totalGui;
}
public void setTextArea(String myString){
frame.append(myString);
}
}
ParceListener
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ParceListener implements ActionListener {
public String text = "some text";
MainFormAppearance mainFormAppearance = new MainFormAppearance();
public void actionPerformed(ActionEvent e) {
mainFormAppearance.setTextArea(text);
}
}
It shows NullPointerException at frame.append(myString); in MainFormAppearance class.
Calling MainFormAppearance mainFormAppearance = new MainFormAppearance(); in your ParceListener is creating a new instance of MainFormAppearance which has nothing to do with what is actually been presented on the screen.
You need some way to return information back to the main UI from ParceListener.
This is best accomplished using an Observer Pattern, where ParceListener generates notifications/events when something changes. It shouldn't care about "who" is interested, only that they are.
Let's start with a simple interface...
public interface ParceObserver {
public void parceChanged(String text);
}
MainFormAppearance can now implement this interface and make what ever updates it needs.
public class MainFormAppearance implements ParceObserver {
//...
public void parceChanged(String text) {
frame.append("\n" + text);
}
}
Then thing here is, ParceListener, doesn't care what happens after it's posted the notification.
Now, you just need to pass an instance of ParceObserver to ParceListener
parceListener = new ParceListener(this);
And update ParceListener to make use of it...
public class ParceListener implements ActionListener {
private ParceObserver observer;
public String text = "some text";
public ParceListener(ParceObserver observer) {
this.observer = observer;
}
public void actionPerformed(ActionEvent e) {
if (observer == null) {
return null;
}
observer.parceChanged(text);
}
}
Now it's nicely de-coupled and re-usable.
And, if someone tells you to just pass a reference of the JTextArea or MainFormAppearance to ParceListener, please don't listen to them. It's inappropriate, tightly couples your code and exposes the components to the risk of been modified in ways you never intended them to be
So I'm new to programming and I've been making a program that uses multiple JPanels on a JFrame and other JPanels. I'm using CardLayout to go between different JPanels and I have it working on two different JButtons, but I can't get the last one to return to the main screen.
I've looked for answers, but it seems like most people just forget to use an ActionListener, something that I know I've done. Here's some of the involved classes in my code (There are a lot so I won't include them all, but I can provide any others that are needed).
Here's the JFrame class:
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.*;
public class ElephantCare extends JFrame {
private static final long serialVersionUID = 1L;
private final String MAIN_STRING = "Main";
public JPanel container, main;
private Staff1Panel staff1 = new Staff1Panel();
private Staff2Panel staff2 = new Staff2Panel();
private StaffConfirmPanel staffConfirm = new StaffConfirmPanel();
private WelcomePanel welcome = new WelcomePanel();
private StaffPanel staff = new StaffPanel();
private GallonsPanel gallons = new GallonsPanel();
private ToysPanel toys= new ToysPanel();
private ActionPanel action = new ActionPanel();
public CardLayout card = new CardLayout();
public ElephantCare() {
setSize(400,300);
setTitle("Elephant Care");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
buildPanel();
add(container);
setVisible(true);
}
private void buildPanel() {
main = new JPanel();;
container = new JPanel(card);
container.add(main, MAIN_STRING);
container.add(staff1, "Staff 1");
container.add(staff2, "Staff 2");
main.setLayout(new BorderLayout());
main.add(welcome, BorderLayout.NORTH);
main.add(staff, BorderLayout.WEST);
main.add(gallons, BorderLayout.CENTER);
main.add(toys, BorderLayout.EAST);
main.add(action, BorderLayout.SOUTH);
staff.getStaff1Button().addActionListener(new Staff1Listener());
staff.getStaff2Button().addActionListener(new Staff2Listener());
staffConfirm.getConfirmButton().addActionListener(new ConfirmButtonListener());
}
private class Staff1Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 1");
}
}
private class Staff2Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 2");
}
}
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, MAIN_STRING);
}
}
}
Here's the JPanel with the Button:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
}
public JButton getConfirmButton() {
return confirm;
}
}
And here's the JPanels where the button is used:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff1Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff1NamePanel name = new Staff1NamePanel();
private Staff1JobPanel job = new Staff1JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff1Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
And:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff2Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff2NamePanel name = new Staff2NamePanel();
private Staff2JobPanel job = new Staff2JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff2Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
Thanks for any help!
There are a lot of things happening in this code that are not quite right, and they contribute to your issue. So lets address the biggest issue and then you will need to do the rest yourself.
First edit this code to have some debug text:
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(container, MAIN_STRING);
}
}
Now when you run your code you will notice that the message "ConfirmButtonListener was triggered" will never be printed to the console, meaning that the code never runs, so you can never return to the main screen.
This happens because you create a StaffConfirmPanel named staffConfirm in your ElephantCare class. Then you add an action listener to staffConfirm. The problem is that you never use that staffconfirm panel anywhere because you create a new StaffConfirmPanel inside Staff1Panel and Staff2Panel so the action listener you have made will do nothing.
So the solution is to move the whole ConfirmButtonListener method and the staffConfirm.getConfirmButton().addActionListener line into the StaffConfirmPanel class like so:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
//NEW LINE: SET THE ACTION LISTENER
confirm.addActionListener(new ConfirmButtonListener());
}
public JButton getConfirmButton() {
return confirm;
}
//NEW CODE, MOVE THE ACTION LISTENER METHOD HERE
//ACTION LISTENER MOVED HERE
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(ElephantCare.container, ElephantCare.MAIN_STRING);
}
}
}
Now it should work correctly and return to the main screen.
EDIT: you will need to make MAIN_STRING a public variable in the ElephantCare class.
Alright, since it didn't work out last time. I'm going to post my full code here and i hope i can get some replies as to why it's not working. I'm not getting any compiling errors, the applet runs and then nothing shows up and at the bottom it says "applet not initialized." I'm using blueJ. I apologize for the length of this post, I could not figure out how to make this same error with a shorter code.
I have a JApplet program with multiple classes. RegPanel,WorkshopPanel, ConferenceGUI, ConferenceHandler and ConferenceClient. Basically RegPanel and WorkShop panel are added to the ConferenceGUI, which also creates and adds a couple small panels. The ConferenceClient class is just used to initaite the class to run the applet. The ConferenceHandler is used to handle the action events for the JButtons, JTextArea, JCheckBox, etc... Normally this whole program works fine. Except when i add a listener for the JCheckBox, it stops working. The problem area is in the ConferenceGUI class, it is commented with stars to be clear what's causing the problem.
I've been stuck on this error for about a day now and the frustration i'm feeling is overwhelming. So, to get to the point, here's the complete code:
(please, i don't need tips on any other part of the code, I just need help with that error). You may want to skip over the code and just read the ConferenceGUI class where the error is located. If you could also explain to me why this isn't working, it would be very helpful to me. Thank you in advance!
RegPanel Class:
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class RegPanel extends JPanel
{
protected JTextField regNameTextBox;
protected JCheckBox keynoteCheckBox;
protected final String[] REGTYPES = {"Please select a type","Business","Student","Complimentary"};
protected JPanel registrationPanel, keynotePanel;
protected final double BUSINESSFEE = 895,STUDENTFEE = 495,COMPLIMENTARYFEE = 0;
protected JComboBox regTypeComboBox;
public RegPanel()
{
//Set the layout for the RegPanel to be 2 rows and 1 column.
setLayout(new GridLayout(2, 1));
//initiate the registration panel and add a border
registrationPanel = new JPanel();
registrationPanel.setLayout(new FlowLayout());
registrationPanel.setBorder(BorderFactory.createTitledBorder("Registrant's Name & Type"));
//initiate the comboBox and add the registration types
regTypeComboBox = new JComboBox(REGTYPES);
//Initiate the textfield with a size of 20
regNameTextBox = new JTextField(20);
//Add the registration name textbox and type combobox to the registration panel
registrationPanel.add(regNameTextBox);
registrationPanel.add(regTypeComboBox);
//initiate the second panel for the checkbox
keynotePanel = new JPanel();
keynotePanel.setLayout(new FlowLayout());
//initiate the checkbox and add it to the keynote panel
JCheckBox keynoteCheckBox = new JCheckBox("Dinner and Keynote Speach");
keynotePanel.add(keynoteCheckBox);
//Add the two panels to the main panel
add(registrationPanel);
add(keynotePanel);
}
public double getRegistrationCost()
{
double regFee = 0;
String comboBoxAnswer = (String)regTypeComboBox.getSelectedItem();
switch (comboBoxAnswer)
{
case "Business": regFee = BUSINESSFEE;
break;
case "Student": regFee = STUDENTFEE;
break;
}
return regFee;
}
public double getKeynoteCost()
{
double keynoteCost = 0;
if(keynoteCheckBox.isSelected())
{
keynoteCost = 30;
}
return keynoteCost;
}
public String getRegType()
{
String regType = (String)regTypeComboBox.getSelectedItem();
return regType;
}
}
WorkshopPanel Class:
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class WorkshopPanel extends JPanel
{
protected final double ITFEE = 295, DREAMFEE = 295, JAVAFEE = 395, ETHICSFEE = 395;
protected final String[] WORKSHOPS = {"IT Trends in Manitoba","Creating a Dream Career","Advanced Java Programming","Ethics: The Challenge Continues"};
protected JList workshopList;
public WorkshopPanel()
{
setLayout(new FlowLayout());
workshopList = new JList(WORKSHOPS);
workshopList.setSelectionMode(2);
BorderFactory.createTitledBorder("Workshops");
add(workshopList);
}
public double getWorkshopCost()
{
Object[] workshops = workshopList.getSelectedValues();
double cost = 0;
String workshopString;
for (int i = 0; i < workshops.length; i++)
{
workshopString = (String)workshops[i];
switch(workshopString)
{
case "IT Trends in Manitoba":
cost += ITFEE;
break;
case "Creating a Dream Career":
cost += DREAMFEE;
break;
case "Advanced Java Programming":
cost += JAVAFEE;
break;
case "Ethics: The Challenge Continues":
cost += ETHICSFEE;
break;
}
}
return cost;
}
public Object[] getWorkshopList()
{
Object[] workshopListArray = workshopList.getSelectedValues();
return workshopListArray;
}
}
ConferenceGUI class (THIS CONTAINS THE ERROR):
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class ConferenceGUI extends JPanel
{
protected JPanel titlePanel, buttonPanel;
protected RegPanel regPanel;
protected WorkshopPanel workshopPanel;
protected JLabel titleLabel;
protected JButton calculateButton, clearButton;
protected JTextArea resultArea;
protected JScrollPane textScroll;
public ConferenceGUI()
{
setLayout(new BorderLayout());
titlePanel = new JPanel();
titleLabel = new JLabel("Select Registration Options",JLabel.CENTER);
Font titleFont = new Font("SansSerif", Font.BOLD, 18);
titleLabel.setFont(titleFont);
titlePanel.add(titleLabel);
add(titlePanel, BorderLayout.NORTH);
regPanel = new RegPanel();
add(regPanel, BorderLayout.WEST);
workshopPanel = new WorkshopPanel();
add(workshopPanel, BorderLayout.EAST);
buildButtonPanel();
add(buttonPanel, BorderLayout.SOUTH);
ConferenceHandler handler = new ConferenceHandler(this);
regPanel.regTypeComboBox.addItemListener(handler);
regPanel.regNameTextBox.addFocusListener(handler);
//****************************************************************
//The line below is what causes the error. Without it the code
//Works, with it it doesn't and i get the aforementioned error.
//regPanel.keynoteCheckBox.addItemListener(handler);
}
private void buildButtonPanel()
{
buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
calculateButton = new JButton("Calculate Charges");
buttonPanel.add(calculateButton);
clearButton = new JButton("Clear");
buttonPanel.add(clearButton);
resultArea = new JTextArea(5,30);
textScroll = new JScrollPane(resultArea);
buttonPanel.add(textScroll);
ConferenceHandler handler = new ConferenceHandler(this);
calculateButton.addActionListener(handler);
clearButton.addActionListener(handler);
}
}
ConferenceHandler class( this class is unfinished until i get that error straightened out) :
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class ConferenceHandler implements ActionListener, ItemListener, FocusListener
{
protected ConferenceGUI gui;
public ConferenceHandler(ConferenceGUI gui)
{
this.gui = gui;
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == gui.calculateButton)
{
String regType = gui.regPanel.getRegType();
Object[] workshopList = gui.workshopPanel.getWorkshopList();
String workshopString;
if (regType == "Please select a type")
{
JOptionPane.showMessageDialog(null,"Please select a registration type","Type Error",JOptionPane.ERROR_MESSAGE );
}
else
{
if(gui.regPanel.keynoteCheckBox.isSelected())
{
gui.resultArea.append("Keynote address will be attended/n");
}
else
{
gui.resultArea.append("Keynot address will not be attended/n");
}
}
}
if (e.getSource() == gui.clearButton)
{
gui.resultArea.append("CLEAR");
}
}
private double getTotalCharges()
{
double charges = 0;
return charges;
}
public void itemStateChanged(ItemEvent e)
{
}
public void focusLost(FocusEvent e)
{
}
public void focusGained(FocusEvent e)
{
}
}
ConferenceClient Class:
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class ConferenceClient extends JApplet
{
private final int WINDOW_HEIGHT = 700, WINDOW_WIDTH = 250;
private ConferenceGUI gui;
private Container c;
public ConferenceClient()
{
gui = new ConferenceGUI();
c = getContentPane();
c.setLayout(new BorderLayout());
c.add(gui, BorderLayout.CENTER);
setSize(WINDOW_HEIGHT, WINDOW_WIDTH);
}
}
You're shadowing your keynoteCheckBox variable. First you create a instance field in RegPanel, but in the constructor, you redeclare it...
public class RegPanel extends JPanel {
protected JCheckBox keynoteCheckBox;
//...
public RegPanel() {
//...
//initiate the checkbox and add it to the keynote panel
JCheckBox keynoteCheckBox = new JCheckBox("Dinner and Keynote Speach");
keynotePanel.add(keynoteCheckBox);
This leaves the instance field as null which will cause a NullPointerException
Also, this: regType == "Please select a type" is not how to compare Strings in Java, you want to use something more like "Please select a type".equals(regType)
I'm using IntelliJ GUI Builder to design a GUI for my application. In it, there is a JTable inside a JScrollPane that doesn't seem to be working. Firstly, I can't get the column headers to display. Second, table clicking is not working. It acts as if I'm clicking 3 rows down from where I actually am, both in default row selection and in any MouseListeners I implement. Lastly, if the table exceeds the size of the JScrollPane, it just ignores the last X rows and doesn't provide a scroll bar to view them.
I've reworked the project a couple times now, trying extensions of AbstractTableModel, then DefaultTableModel, and lately I have tried ditching a custom TableModel altogether and just using a DefaultTableModel constructor to no avail. Here is all relevant code (some of it is auto-generated by the GUI Builder and I can't modify it directly).
BaldGUI.java (the main gui)
package client;
import client.DataTypes.Record;
import client.DataTypes.RecordSet;
import client.GuiElements.FileTree;
import client.GuiElements.RecordsTable;
import client.GuiElements.TextConsole;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
public class BaldGUI extends JFrame {
//Menu
private JMenuBar menuBar = new JMenuBar();
private JMenu fileMenu = new JMenu("File");
private JMenuItem newBatchItem = new JMenuItem("New Batch");
private JMenuItem saveBatchItem = new JMenuItem("Save Batch");
private JMenuItem loadBatchItem = new JMenuItem("Load Batch");
private static String rootDir = "C:/Users/wf1946/IdeaProjects/DocumentumLoaderTest01/data";
private JPanel mainPanel;
private JPanel LeftSideBarPanel;
private JTree fileTree;
private JButton AddFileButton;
private JButton ChangeDirectoryButton;
private JButton AddDirectoryButton;
private JCheckBox IncludeSubDirectoriesCheckBox;
private JScrollPane DataTableWrapper;
private JTable DataTable;
private JEditorPane Console;
private JScrollPane ConsoleScroller;
public BaldGUI() {
$$$setupUI$$$();
this.loadComponents();
this.AddFileButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
((RecordsTable) DataTable).addItem(new Record());
}
});
this.add(this.mainPanel);
}
private void loadComponents() {
//Menu
this.setJMenuBar(this.menuBar);
this.menuBar.add(this.fileMenu);
this.fileMenu.add(this.newBatchItem);
this.fileMenu.add(this.saveBatchItem);
this.fileMenu.add(this.loadBatchItem);
//Selection handler for the file tree
this.fileTree.addTreeSelectionListener(new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent e) {
TreePath path = e.getPath();
if (!fileTree.getModel().isLeaf(path.getLastPathComponent())) { //Directory
AddDirectoryButton.setEnabled(true);
IncludeSubDirectoriesCheckBox.setEnabled(true);
AddFileButton.setEnabled(false);
} else { //File
AddFileButton.setEnabled(true);
AddDirectoryButton.setEnabled(false);
IncludeSubDirectoriesCheckBox.setEnabled(false);
}
}
});
}
//Getters
public JEditorPane getConsole() {
return Console;
}
public JPanel getMainPanel() {
return mainPanel;
}
public JTree getFileTree() {
return fileTree;
}
public JTable getDataTable() {
return this.DataTable;
}
public JCheckBox getIncludeSubDirectoriesCheckBox() {
return IncludeSubDirectoriesCheckBox;
}
public JScrollPane getDataTableWrapper() {
return DataTableWrapper;
}
private void createUIComponents() {
this.Console = new TextConsole();
this.fileTree = new FileTree(this, new File(this.rootDir));
RecordSet rs = new RecordSet();
for (int i = 0; i < 10; i++) rs.add(new Record());
this.DataTable = new RecordsTable(new DefaultTableModel(rs.getData(), RecordsTable.colNames), this);
this.DataTableWrapper = new JScrollPane(this.DataTable);
}
/**
* Method generated by IntelliJ IDEA GUI Designer
* >>> IMPORTANT!! <<<
* DO NOT edit this method OR call it in your code!
*
* #noinspection ALL
*/
private void $$$setupUI$$$() {
createUIComponents();
mainPanel = new JPanel();
mainPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
mainPanel.setMinimumSize(new Dimension(1080, 810));
mainPanel.setPreferredSize(new Dimension(1080, 810));
LeftSideBarPanel = new JPanel();
LeftSideBarPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
LeftSideBarPanel.setMinimumSize(new Dimension(220, 35));
LeftSideBarPanel.setPreferredSize(new Dimension(220, 600));
mainPanel.add(LeftSideBarPanel);
fileTree.setPreferredSize(new Dimension(200, 530));
fileTree.setShowsRootHandles(true);
LeftSideBarPanel.add(fileTree);
AddFileButton = new JButton();
AddFileButton.setPreferredSize(new Dimension(100, 25));
AddFileButton.setText("Add File");
LeftSideBarPanel.add(AddFileButton);
ChangeDirectoryButton = new JButton();
ChangeDirectoryButton.setPreferredSize(new Dimension(100, 25));
ChangeDirectoryButton.setText("Change Root");
LeftSideBarPanel.add(ChangeDirectoryButton);
AddDirectoryButton = new JButton();
AddDirectoryButton.setPreferredSize(new Dimension(100, 25));
AddDirectoryButton.setText("Add Directory");
LeftSideBarPanel.add(AddDirectoryButton);
IncludeSubDirectoriesCheckBox = new JCheckBox();
IncludeSubDirectoriesCheckBox.setPreferredSize(new Dimension(100, 22));
IncludeSubDirectoriesCheckBox.setText("Subdirectories");
LeftSideBarPanel.add(IncludeSubDirectoriesCheckBox);
DataTableWrapper.setPreferredSize(new Dimension(845, 600));
mainPanel.add(DataTableWrapper);
DataTable.setFillsViewportHeight(true);
DataTableWrapper.setViewportView(DataTable);
ConsoleScroller = new JScrollPane();
mainPanel.add(ConsoleScroller);
Console.setEnabled(false);
Console.setPreferredSize(new Dimension(1070, 195));
ConsoleScroller.setViewportView(Console);
}
/**
* #noinspection ALL
*/
public JComponent $$$getRootComponent$$$() {
return mainPanel;
}
}
RecordsTable.java
package client.GuiElements;
import client.ActionListeners.RightClickMenuItemClick;
import client.ActionListeners.TableRightClickHandler;
import client.BaldGUI;
import client.DataTypes.Record;
import client.DataTypes.RecordSet;
import javax.swing.*;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.util.ArrayList;
//Table to store the records
public class RecordsTable extends JTable {
//Status codes returned to calling functions to indicate the success or failure of the new record
public static final int APPEND_SUCCESS_CODE = 1;
public static final int APPEND_FAIL_DUPLICATE_CODE = 2;
public static final String[] colNames = {"Status", "File", "Full Path", "Title", "Form Date",
"Form No.", "Language Code", "Filed", "Approval Date", "Filed Form No."};
private RecordSet data = new RecordSet();
//Parent form
BaldGUI parent;
//Right-click menu for table item
JPopupMenu itemRightClickMenu = new JPopupMenu();
JMenuItem itemEdit = new JMenuItem("Edit Record");
JMenuItem itemDelete = new JMenuItem("Remove Record");
public RecordsTable(DefaultTableModel model, BaldGUI form) {
super(model);
this.parent = form;
this.itemRightClickMenu.add(itemEdit);
this.itemRightClickMenu.add(itemDelete);
this.itemEdit.addMouseListener(new RightClickMenuItemClick(this, itemEdit));
this.itemDelete.addMouseListener(new RightClickMenuItemClick(this, itemDelete));
this.addMouseListener(new TableRightClickHandler(this));
this.updateTable();
}
//Attempts to add a new row to the table
//Returns APPEND_FAIL_DUPLICATE_CODE if the selected file is already in the table
//Returns APPEND_SUCCESS_CODE if the record is successfully added
public int addItem(Record newRecord) {
TextConsole tc = ((TextConsole)this.parent.getConsole());
if(this.itemInData(newRecord)) {
tc.addText(
"File " + newRecord.getFileName() + " already included.\n", TextConsole.redStyle
);
return this.APPEND_FAIL_DUPLICATE_CODE;
}
this.data.add(newRecord);
tc.addText("File " + newRecord.getFileName() + " added successfully.\n", TextConsole.greenStyle);
this.updateTable();
return this.APPEND_SUCCESS_CODE;
}
//Updates the table to display any new data
public void updateTable() {
}
//Returns true if the record is already in the table
//Record equality is defined based on the full path to the file
public boolean itemInData(Record item) {
for( Record r : data) {
if(r.equals(item)) return true;
}
return false;
}
public JPopupMenu getItemRightClickMenu() {
return itemRightClickMenu;
}
public JMenuItem getItemEdit() {
return itemEdit;
}
public BaldGUI getParent() {
return parent;
}
}
The Record type is just a basic data container, and RecordSet is just an extension of ArrayList{Record} with a method to turn the data therein into an Object[][] for the DefaultTableModel.
So, as I expected, it was a really simple, dumb mistake. In my RecordsTable class, I stored off the parent GUI (BaldGUI) as a variable called parent. I then had a method getParent() to fetch that parent, and I didn't realize that JTable comes with a method getParent() which gets the surrounding component. By overriding that method, the entire program more or less broke. I changed the method, and it works as it should.
I have created a JTable with GlazedList eventList.
I searched the API but i couldn't figure out how to add undo/redo possibility to this table. I found in the API following classes:
UndoRedoSupport undoRedoSupport = new UndoRedoSupport<"what here?">("argument?");
UndoSupport undoSupport = new UndoSupport<"what to write here?">("argument?");
Does any body know how to use it?
private void createComponents() {
EventList<Dien> eventList = new BasicEventList<Dien>();
actionList = GenericsUtil.makeList();
table = new WebTable();
searchField = new WebTextField(60);
String[] headers = new String[]{"Code", "Name", "Number"};
String[] properties = new String[]{"Code", "Name", "Number"};
TextFilterator<Dien> dienFilterator = new TextFilterator<Dien>() {
public void getFilterStrings(List baseList, Dien dien) {
baseList.add(dien.getCode());
baseList.add(dien.getName());
baseList.add(dien.getNumber());
}
};
MatcherEditor<Dien> textMatcherEditor = new TextComponentMatcherEditor<Dien>(searchField, dienFilterator);
eventList = toolModel.getDiens();
FilterList<Dien> filterList = new FilterList<Dien>(eventList, textMatcherEditor);
TableFormat tableFormat = GlazedLists.tableFormat(properties, headers, new boolean[]{true,true,true});
model = new EventTableModel<Dien>(filterList, tableFormat);
model.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if(e.getType()==TableModelEvent.UPDATE){
if(!panel.isPendingChanges())
panel.setPendingChange(true);
}
}
});
selectionModel = new EventSelectionModel<Dien>(filterList);
table.setSelectionModel(selectionModel);
table.setModel(model);
}
The Undo/Redo classes built in to GlazedLists does not come with a public constructor; instead you install support to a particular eventlist via the UndoRedoSupport.install() static method.
Of course, if you're using Swing then it makes sense to leverage Swing's UndoManager class and GlazedLists provides a simple wrapper with its UndoSupport class. Again this is simply initialised with its install() method.
I've created a simple Swing sample application as an example to illustrate how to use these classes. In my example I'm using a simple EventList of Strings and a JList. But it will apply the same to any GlazedList-backed component -- UndoRedoSupport applies to the EventList itself and not the Swing component.
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventListModel;
import ca.odell.glazedlists.swing.UndoSupport;
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.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.undo.UndoManager;
public class GlazedListsUndoSample {
private JFrame mainFrame;
private JButton addItemButton;
private JButton undoButton;
private JButton redoButton;
private UndoManager undoManager;
private EventList<String> eventList = new BasicEventList<String>();
public GlazedListsUndoSample() {
//populateAvailableBooks();
createGui();
mainFrame.setVisible(true);
}
private void updateButtons() {
//addBookButton.setEnabled(!books.isEmpty());
undoButton.setEnabled(undoManager.canUndo());
redoButton.setEnabled(undoManager.canRedo());
}
private void createGui() {
undoManager = new UndoManager();
UndoSupport.install(undoManager, eventList);
mainFrame = new JFrame("GlazedLists Undo Example");
mainFrame.setSize(600, 400);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EventListModel model = new EventListModel(eventList);
JList list = new JList(model);
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(new JScrollPane(list), BorderLayout.CENTER);
JPanel addBookPanel = new JPanel(new BorderLayout());
addBookPanel.add(new JLabel("Item"), BorderLayout.WEST);
final JTextField titleTextField = new JTextField(50);
addBookPanel.add(titleTextField, BorderLayout.CENTER);
addItemButton = new JButton("Add Item");
addItemButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
eventList.add(titleTextField.getText());
updateButtons();
}
});
addBookPanel.add(addItemButton, BorderLayout.EAST);
JPanel buttonPanel = new JPanel();
undoButton = new JButton("Undo");
undoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canUndo()) {
undoManager.undo();
}
updateButtons();
}
});
redoButton = new JButton("Redo");
redoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canRedo()) {
undoManager.redo();
}
updateButtons();
}
});
updateButtons();
buttonPanel.add(undoButton);
buttonPanel.add(redoButton);
mainPanel.add(addBookPanel, BorderLayout.NORTH);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
mainFrame.getContentPane().setLayout(new BorderLayout());
mainFrame.getContentPane().add(mainPanel, BorderLayout.CENTER);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GlazedListsUndoSample();
}
});
}
}
It's worth noting that the documentation does strongly hint at its functional limitations:
Not every change described in a ListEvent results in an undoable edit.
Specifically, a mutation of a list element IN PLACE does not produce
an undoable edit. For example, an ObservableElementList which observes
a change of an element, or a call to List.set(int, E) with the same
object at that index produce a ListEvent that does not have a
corresponding UndoRedoSupport.Edit object. These ListEvents are
ignored because they lack sufficient information to undo or redo the
change.
In general UndoRedoSupport only makes sense for use with a
BasicEventList or a trivial wrapper around a BasicEventList which does
not affect the order or type of the elements, such as an
ObservableElementList. Advanced transformations, such as SortedList or
FilterList will not work as expected with this UndoRedoSupport class
since their contents are controlled by information outside of
themselves (Comparators and Matchers).