4
\$\begingroup\$

I watched bunch of videos about swing and GUI in general and created my first component (correct me if it is not one).

JPanel class:

package gui.pane;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;


public class CalendarPanel extends JPanel {
    private List<JButton> dateButtons = new ArrayList<>();
    private Calendar selectedDate;
    private final JPanel datePanel;
    private EventListenerList listenerList = new EventListenerList();

    public CalendarPanel() {
        this(Calendar.getInstance());
    }

    public CalendarPanel(Calendar selectedDate) {
        this.selectedDate = selectedDate;
        setBorder(BorderFactory.createTitledBorder("Calendar"));

        // year combo box
        final JComboBox yearComboBox = new JComboBox<>(getYearList(selectedDate.get(Calendar.YEAR)));
        yearComboBox.addActionListener( e -> {
                selectedDate.set(Calendar.YEAR, (Integer) yearComboBox.getSelectedItem());
                updateDateButtons();
            }
        );

        //month combo box
        final JComboBox monthComboBox = new JComboBox<>(new DateFormatSymbols().getMonths());
        monthComboBox.setSelectedIndex(selectedDate.get(Calendar.MONTH));
        monthComboBox.addActionListener( e -> {
                selectedDate.set(Calendar.MONTH, monthComboBox.getSelectedIndex());
                updateDateButtons();
             }
        );

        // creating panels
        final JPanel calendarPanel = new JPanel();
        calendarPanel.setLayout(new BorderLayout());

        // create month and year panel
        final JPanel MonthAndYearPanel = new JPanel();
        MonthAndYearPanel.setLayout(new FlowLayout());

        // add year and month combo boxes to sub panel
        MonthAndYearPanel.add(yearComboBox);
        MonthAndYearPanel.add(monthComboBox);

        // creates date panel
        datePanel = new JPanel();
        datePanel.setLayout(new GridBagLayout());
        createDateButtons();

        // add sub panels to main panel
        calendarPanel.add(MonthAndYearPanel, BorderLayout.PAGE_START);
        calendarPanel.add(datePanel, BorderLayout.PAGE_END);

        add(calendarPanel);

    }

    private Integer[] getYearList(int year) {
        Integer[] arr = new Integer[10];
        for(int index = 0; index < 10; index++) {
            arr[index] = year + index;
        }
        return arr;
    }

    private void updateDateButtons(){
        deleteDateButtons();
        createDateButtons();
    }

    private void deleteDateButtons() {
        dateButtons.stream().forEach(datePanel::remove);
        dateButtons = new ArrayList<>();
        datePanel.revalidate();
        datePanel.repaint();
    }

    private void createDateButtons() {
        // sets date to first date of month
        selectedDate.set(Calendar.DATE, 1);

        // initializes grid
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.anchor = GridBagConstraints.PAGE_START;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weightx = 1;
        gbc.weighty = 1;
        gbc.insets = new Insets(2,2,2,2);

        // placing buttons on the grid
        gbc.gridy = 0;
        gbc.gridx = selectedDate.get(Calendar.DAY_OF_WEEK) - 1;
        for(int index = 0; index < selectedDate.getActualMaximum(Calendar.DAY_OF_MONTH); index++) {
            JButton button = new JButton(String.valueOf(index + 1));

            // adds actions listener to button
            button.addActionListener( e -> {
                selectedDate.set(Calendar.DATE, dateButtons.indexOf((JButton) e.getSource()) + 1);
                CalendarEvent calendarEvent = new CalendarEvent(e,selectedDate);
                fireCalendarEvent(calendarEvent);
            });

            datePanel.add(button, gbc);
            dateButtons.add(button);
            gbc.gridx++;
            if (gbc.gridx == 7) {
                gbc.gridy++;
                gbc.gridx = 0;
            }
        }

        // resetting panel and date
        datePanel.revalidate();
        datePanel.repaint();
    }

    public void fireCalendarEvent(CalendarEvent event) {
        Object[] listeners = listenerList.getListenerList();

        for(int index = 0; index < listeners.length; index += 2) {
            if (listeners[index] == CalendarListener.class) {
                ((CalendarListener) listeners[index + 1]).CalendarEventOccurred(event);
            }
        }
    }

    public void addCalendarListener(CalendarListener listener) {
        listenerList.add(CalendarListener.class, listener);

    }

    public void removeCalendarListener(CalendarListener listener) {
        listenerList.remove(CalendarListener.class, listener);
    }
}

CalendarEvent class:

package gui.pane;

import java.util.Calendar;
import java.util.EventObject;

public class CalendarEvent extends EventObject {

    private Calendar date;

    public CalendarEvent(Object source, Calendar date) {
        super(source);
        this.date = date;
    }

    public Calendar getDate() {
        return date;
    }

}

CalendarListener class:

package gui.pane;

import java.util.EventListener;

public interface CalendarListener extends EventListener {
    public void CalendarEventOccurred(CalendarEvent event);
}

and CinemaFrame class:

package gui;

import gui.pane.CalendarPanel;

import javax.swing.*;
import java.awt.*;

public class CinemaFrame extends JFrame {

    public CinemaFrame(String title) {
        super(title);
        setLayout(new GridBagLayout());
        CalendarPanel calendarPanel = new CalendarPanel();
        calendarPanel.addCalendarListener( e -> System.out.println(e.getSource()));
        add(calendarPanel);

    }
}

Basically this is little part of my homework.

I have some questions:

  1. I couldn't figure out a way to format this code so it wouldn't look messy, that why I added a lot of comments that doesn't really mean anything. I could make a bunch of functions, but then I would have to pass lot of arguments. That does not seem any less messy. How do you approach this?

  2. Also I try to use latest features: lambda expressions, streams. There was places I couldn't figure out how to use them in some situations.

    For example, here:

    private Integer[] getYearList(int year) {
        Integer[] arr = new Integer[10];
        for(int index = 0; index < 10; index++) {
            arr[index] = year + index;
        }
        return arr;
    }
    

    I only could create int array: IntStream.range(0, 10).map(i -> year + i).toArray();

    Tell me where I could use something better then I have.

  3. What else is on your mind to improving this code(patterns, naming, other built in classes, etc..)?

Calendar image

\$\endgroup\$
3
\$\begingroup\$

I think you are being too harsh on yourself. Your code is not particularly messy. It's quite difficult to build up a Swing component without code starting to feel a little untidy. I occasionally split such code into builder methods (e.g. private JPanel buildSomeComplicatedPanel()), but I think your code is fine in that regard.


To answer your stream question, if you use boxed() you can convert a primitive int stream into an Integer stream:

IntStream.range(0, 10).boxed().map(i -> i + year).toArray(size -> new Integer[size]);

However, since IntStream::range takes start and end parameters, you can simply this to:

IntStream.range(year, year + 10).boxed().toArray(size -> new Integer[size]);

Other observations:

  • Your JComboBox objects should use generic types. I.e. JComboBox<Integer> yearComboBox and JComboBox<String> monthComboBox.

  • Most of your classes are missing a serialVersionUID value. Your IDE ought to prompt you about this. (Eclipse certainly does).

  • (Very minor, more of an FYI...) Methods declared in interfaces are always public, so you don't need to include that keyword in each method signature.

  • Your CinemaFrame class extends JFrame. However, most Swing gurus would encourage you to use composition rather than inheritance:

    public class CinemaFrame {
    
      private final JFrame frame;
    
      public CinemaFrame(String title) {
    
        frame = new JFrame(title);
        frame.setLayout(new GridBagLayout());
        CalendarPanel calendarPanel = new CalendarPanel();
        calendarPanel.addCalendarListener(e -> System.out.println(e.getSource()));
        frame.add(calendarPanel);
        frame.pack(); // You may wish to do this in the constructor
      }
    
      public void showFrame() {
        frame.setVisible(true);
      }
    
      // Example main method I used for testing...
      public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(() -> new CinemaFrame("Foo").showFrame());
      }
    }
    
  • Closing your frame doesn't currently end your program. You may want to add frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); into your CinemaFrame class.

\$\endgroup\$
  • 2
    \$\begingroup\$ Most of your classes are missing a serialVersionUID value. Your IDE ought to prompt you about this. (Eclipse certainly does). - It does, but IMHO it should not. By defining it, you commit yourself to handle class changes, which may get damn complicated (and you probably won't do it) . Getting an InvalidClassException in such a case is better than getting an incomplete object. \$\endgroup\$ – maaartinus Apr 3 '15 at 7:54
  • \$\begingroup\$ @maaartinus That's a good point. I doubt many people remember to change the value when they make breaking changes to the class design. \$\endgroup\$ – Duncan Jones Apr 3 '15 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.