0

I'm a Java newbie and I'm working on my first Java project. So far it consists of a GUI with a dynamically generated form (adapted from this question), a Save button and a Load button. With the Save button, the entire form is saved as an object, and with the Load button, an existing saved form can be loaded. Saving and loading themselves seem to work, but the GUI is not updated properly with the loaded form at the point where I (indirectly, i.e. via another function) call revalidate() and repaint() within the actionPerformed method of the Load button:

public class LoadListener extends SingleFileChooser implements ActionListener {

    private static final long serialVersionUID = -4418195536438874952L; 
    private EntryList listFromFile;
    private ScrollBar panel;

    @SuppressWarnings("hiding")
    public LoadListener(String choosertitle, ScrollBar panel) {
        super(choosertitle);
        this.panel = panel;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);       
        try {
            InputStream is = new FileInputStream(this.getFilePath());
            ObjectInputStream ois = new ObjectInputStream(is);
            listFromFile = (EntryList) ois.readObject();
            ois.close();
            panel.setEntryList(listFromFile);
        } catch (FileNotFoundException e1) {
            System.out.println("File not found: " + this.getFilePath());
            e1.printStackTrace();
        } catch (IOException e1) {
            System.out.println("IO exception: " + this.getFilePath());
            e1.printStackTrace();
        } catch (ClassNotFoundException e1) {
            System.out.println("Class for input object not found: " + this.getFilePath());
            e1.printStackTrace();
        }
    }

    public EntryList getLoadedGatingList() {
        return listFromFile;
    }
}

Herein, panel is of a subclass of JPanel:

public class ScrollBar extends JPanel {

    private static final long serialVersionUID = -3460555902426579496L;
    private JScrollPane scrollPane;
    private JButton saveList, loadList;
    private EntryList entryList;

    @SuppressWarnings("hiding")
    public ScrollBar(EntryList entryList, MainFrame frame) {
        scrollPane = new JScrollPane(entryList,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setPreferredSize(new Dimension(300,400));        
        this.entryList = entryList;
        saveList = new JButton("Save List");
        saveList.addActionListener(new SaveListener("Save List", this));
        loadList = new JButton("Load List");
        loadList.addActionListener(new LoadListener("Load List", this));
        add(scrollPane);
        add(saveList);
        add(loadList);
    }

    public EntryList getEntryList() {
        return entryList;
    }

    public void setEntryList(EntryList list) {
        entryList = list;
        this.saveList.setBackground(Color.PINK);
        this.saveList.setText(entryList.getFirstText());
        this.loadList.setBackground(Color.GREEN);
        revalidate();
        repaint();
    }
}

The remaining classes that are needed to run the example are listed below:

public class Entry extends JPanel implements Serializable{

    private static final long serialVersionUID = 8748191176188997955L;
    private JTextField textField;
    private JButton plus;
    private JButton minus;
    private EntryList parent;

    public Entry(String textFieldText, EntryList list) {
        this.parent = list;
        this.plus = new JButton(new AddEntryAction());
        this.minus = new JButton(new RemoveEntryAction());
        this.textField = new JTextField(10);
        this.textField.setText(textFieldText);
        add(this.plus);
        add(this.minus);
        add(this.textField);
    }

    public String getText() {
        return this.textField.getText();
    }

    public class AddEntryAction extends AbstractAction {

        private static final long serialVersionUID = -1936452299010320790L;

        public AddEntryAction() {
            super("+");
        }

        public void actionPerformed(ActionEvent e) {
            parent.cloneEntry(Entry.this);
        }

    }

    public class RemoveEntryAction extends AbstractAction {

        private static final long serialVersionUID = 4843871176230776949L;

        public RemoveEntryAction() {
            super("-");
        }

        public void actionPerformed(ActionEvent e) {
            parent.removeItem(Entry.this);
        }
    }

    public void enableAdd(boolean enabled) {
        this.plus.setEnabled(enabled);
    }
    public void enableMinus(boolean enabled) {
        this.minus.setEnabled(enabled);
    }
}


public class EntryList extends JPanel implements Serializable{

    private static final long serialVersionUID = 1426379083556312697L;
    private List<Entry> entries;

    public EntryList() {
        this.entries = new ArrayList<Entry>();
        Entry initial = new Entry("debugtext", this);
        addItem(initial);
    }

    public int getLength() {
        return this.entries.size();
    }
    public String getFirstText() {
        return this.entries.get(0).getText();
    }

    public void cloneEntry(Entry entry) {
        Entry theClone = new Entry("", this);
        addItem(theClone);
    }

    private void addItem(Entry entry) {
        entries.add(entry);
        add(entry);
        refresh();
    }

    public void removeItem(Entry entry) {
        entries.remove(entry);
        remove(entry);
        refresh();
    }

    private void refresh() {
        revalidate();

        if (entries.size() == 1) {
            entries.get(0).enableMinus(false);
        }
        else {
            for (Entry e : entries) {
                e.enableMinus(true);
            }
        }
    }
}

public class SaveListener extends DirectoryChooser implements ActionListener {

    private static final long serialVersionUID = -8842006866700189526L;
    private EntryList list;
    private String filename;

    public SaveListener(String choosertitle, ScrollBar bar) {
        super(choosertitle, false);
        this.list = bar.getEntryList();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        try {
            filename = this.getResultsDirectory();
            OutputStream os = new FileOutputStream(filename);
            ObjectOutputStream oos = new ObjectOutputStream(os);
            oos.writeObject(list);
            oos.close();
            System.out.println("Length of list at saving = " + this.list.getLength());
            System.out.println("Length of list at saving = " + this.list.getFirstText());

        } catch (FileNotFoundException e1) {
            System.out.println("File not found: " + filename);
            e1.printStackTrace();
        } catch (IOException e1) {
            System.out.println("IO Exception: " + e1);
            e1.printStackTrace();
        }
    }
}

public class DirectoryChooser extends JPanel implements ActionListener {

    private static final long serialVersionUID = -8143869075088499054L;
    private JButton setwd;
    private String workingdir;

    private JFileChooser chooser;
    private String choosertitle;
    private boolean directoryOnly = true;

    @SuppressWarnings("hiding")
    public DirectoryChooser(String choosertitle) {
        this.choosertitle = choosertitle;
        setwd = new JButton(choosertitle);
        setwd.addActionListener(this);
        add(setwd);
        workingdir = new File(System.getProperty("user.dir")).toString();
    }

    @SuppressWarnings("hiding")
    public DirectoryChooser(String choosertitle, boolean directoryOnly) {
        this(choosertitle);
        this.directoryOnly = directoryOnly;
    }

    public void actionPerformed(ActionEvent e) {    

        chooser = new JFileChooser(); 
        chooser.setCurrentDirectory(new java.io.File("."));
        chooser.setDialogTitle(choosertitle);
        if (directoryOnly) {
            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        chooser.setAcceptAllFileFilterUsed(false);

        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { 
            workingdir = chooser.getSelectedFile().toString();
        } else {
            workingdir = chooser.getCurrentDirectory().toString();
        }
    }

    public String getResultsDirectory() {
        return workingdir;
    }

    public Dimension getPreferredSize(){
        return new Dimension(200, 200);
    }
}

public class SingleFileChooser extends JPanel implements ActionListener {

    private static final long serialVersionUID = -9068219101747975546L;
    private JButton loadFiles;
    private JFileChooser chooser;
    private String choosertitle;
    private File file;
    private String filePath;
    private FileNameExtensionFilter filter; 

    @SuppressWarnings("hiding")
    public SingleFileChooser(String choosertitle) {
        this.choosertitle = choosertitle;
        loadFiles = new JButton(choosertitle);
        loadFiles.addActionListener(this);
        add(loadFiles);
    }

    @SuppressWarnings("hiding")
    public SingleFileChooser(String choosertitle, FileNameExtensionFilter filter) {
        this(choosertitle);
        this.filter = filter;
    }

    public void actionPerformed(ActionEvent e) {    

        chooser = new JFileChooser(); 
        chooser.setMultiSelectionEnabled(false);
        if (filter != null) {
            chooser.setFileFilter(filter);
        }
        chooser.setDialogTitle(choosertitle);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);


        int returnVal = chooser.showOpenDialog(null);
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            file = chooser.getSelectedFile();
            filePath = file.getAbsolutePath();
        }
    }

    public File getFile() {
        return file;
    }

    public String getFilePath(){
        return filePath;
    }

    public Dimension getPreferredSize(){
        return new Dimension(200, 200);
    }
}

public class MainFrame extends JFrame {

    private static final long serialVersionUID = 512941639567306317L;
    EntryList panel = new EntryList();
    ScrollBar scrollPanel = new ScrollBar(panel, this);

    public MainFrame() throws HeadlessException {   
        this.getContentPane().add(scrollPanel);
        this.setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS));
    }
}

public class GUImain {

    public static void main(String[] args) {
        MainFrame frame = new MainFrame();
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null); // center the window
    }
}

When I execute this code, modify the text in the text field, save the form and load it again, the saved text is not loaded into the text field and the Load button disappears. The Save button, on the other hand, is repainted: It turns pink and gets the correctly loaded content of the text field.

So my question is: How can I get the saved form loaded correctly and prevent buttons from disappearing?

c0der
  • 18,467
  • 6
  • 33
  • 65
AnjaM
  • 2,941
  • 8
  • 39
  • 62
  • Please post [mcve] that includes the definition of `EntryList`. Consider saving the data needed for reconstructing an `EntryList` to a file, rather than the `EnrtyList` object. – c0der Mar 20 '19 at 09:45
  • @c0der I am not sure whether I can think of a truly mnimal example, but I have simplified the code and made it complete. What exactly do you mean by saving data instead of object? Is it saving the number of entries and their content as a text file and rebuilding the `EntryList` object from this? – AnjaM Mar 20 '19 at 15:21
  • Just in case anyone else has similar problems with GUI update: I found out that when modifying SWING components, you should use the built-in methods (`removeAllItems`, then `addItem` for `JComboBox`; `setListData` for `JList`). I still don't understand why the `setEntryList` method in my `ScrollBar` class doesn't do the job, but switching to the SWING methods seems to solve the problem. – AnjaM Mar 25 '19 at 14:16

1 Answers1

1

Serializing AWT components is not a good idea, for several reasons one being compatibility with future releases. Another is that not all swing objects implement serialisable. For example GridLayout is not serialisable.
It should be much simpler to store all the information needed to reconstruct and EntryList object, and using it when it needs to be reconstructed.
Reconstructing an Entry requires just a String so I would recommend storing all those strings in a file, rather than attempting to save an EntryList object to file.
After retrieving those strings, each representing one Entry, you can easily reconstruct an EntryList.

Add a method for adding an Entry to EntryList:

public void addItem(String text) {
    Entry entry = new Entry(text, this);
    entries.add(entry);
    add(entry);
}

When you want to reconstruct an EntryList :

    //get all stored Entry strings from file into a List (for example) 
    List<String>entriesText = getEntriesStringsFromFile(); 

    EntryList eList = new EntryList(); //make a new EntryList 
    for (String text : entriesText){ //add all content to EntryList
        eList.addItem(text);
    }

Now you have a fully reconstructed EntryList.
Consider adding a constructor that accepts List<String>entriesText to EntryList.


Side note: I think cloneEntry should be:

public void cloneEntry(Entry entry) {
    Entry theClone = new Entry(entry.getText(), this);
    addItem(theClone);
}
c0der
  • 18,467
  • 6
  • 33
  • 65
  • Thanks for your answer. The problem is that the form in the final application will be more complex (not just a text field, but a JComboBox und two Jlists with multi-selections), so `getEntriesStringsFromFile()` might become quite complex. Should I use multiple files for that (e.g. one file for the JComboBoxes, one for the first JList etc. - here I will need a delimiter to know where the JList components of an entry end)? If so, it becomes more complicated for the user to load the form. Also, I assume that there is still a bug in my code (disappearance of one button at loading). – AnjaM Mar 20 '19 at 18:37
  • 1
    "Should I use multiple files for that " personally I would prefer to [read and write into an xml file](https://stackabuse.com/reading-and-writing-xml-in-java/). For more help post another question to conform with one-question-per-post policy. I think the approach I suggested should be simpler to implement. See also [here](https://stackoverflow.com/questions/30262387/how-to-serialize-the-jpanel). As for the button bug: it requires mcve so one can run it and reproduce the problem. – c0der Mar 21 '19 at 05:05
  • Ok, thanks, I'll look at storing the data in a structured way and maybe post another question. Is it generally considered a bad habit to serialize data structures rather than to save data? Also, concerning the button, I edited the question yesterday to simplify the example and posted the complete code, so it should be reproducible now. Probably it's still not minimal, but as I don't have any idea which components are important, I don't know how to minimise it further. – AnjaM Mar 21 '19 at 08:22