1

I am using JDateChooser for a java swing project I am developing and in this, the date could be set in two ways: by the end user or programmatically.

So I have defined a propertychangelistener in the respective class(the variable trig is initialised to zero and maintains track on how many times a property change is listened).

public class WriteEntry{
private int trig=0;
private Date currentDate = new Date();
public JDateChooser dateChooser = new JDateChooser();
public CustomDate selectedDate = DateConverter.convertDate(currentDate);
private static String filename = StorageSpace.currentpath+CurrentUser.getInstance().getUserName()+"\\"+
        Integer.toString(selectedDate.getYear())+"\\"
          +Integer.toString(selectedDate.getMonth())+"\\"+Integer.toString(selectedDate.getDay())+".txt";
private JLabel dayinfo = new JLabel("");
private JTextArea contentfield = new JTextArea("");
private PropertyChangeListener lis = new PropertyChangeListener(){
      @Override
      public void propertyChange(PropertyChangeEvent e) {
          System.out.println("triggered "+trig++);
            if(dateBoundary())  {
                selectedDate = DateConverter.convertDate(dateChooser);
                filename = StorageSpace.currentpath+CurrentUser.getInstance().getUserName()+"\\"+
                        Integer.toString(selectedDate.getYear())+"\\"
                          +Integer.toString(selectedDate.getMonth())+"\\"+Integer.toString(selectedDate.getDay())+".txt";
            }
            else {
                updateDateChooser(selectedDate);
            }
            if(isAlreadyWritten())
            {
                try {
                    updateEditFields(selectedDate, "content");
                } catch (IOException e1) {
                    e1.printStackTrace();
                }   
            }
            else
            {       
                contentfield.setText("Start writing here");
                dayinfo.setText("You are making entry for: "+ new SimpleDateFormat("dd/MM/yyyy").format(dateChooser.getDate()));        
            }
      }      
    };
WriteEntry() //constructor
{
dateChooser.setDateFormatString("dd MM yyyy");
dateChooser.addPropertyChangeListener(lis);
updateEditFields(DateConverter.convertDate(currentDate), "Start");
}
}

And here is the code for dateBoundary():

public static boolean dateBoundary() {
    Object[] option = {"I get it","My Bad!"};
    if(dateChooser.getDate().compareTo(currentDate)>0) {
        JOptionPane.showOptionDialog(HomePage.getFrame(),"message1",
                "",JOptionPane.DEFAULT_OPTION,JOptionPane.ERROR_MESSAGE,null,option,option[0]);
        return false;
    }
    if(dateChooser.getDate().compareTo(DateConverter.convertfromCustom(CurrentUser.getInstance().getDob()))<0){
JOptionPane.showOptionDialog(HomePage.getFrame(),"message2",
                "",JOptionPane.DEFAULT_OPTION,JOptionPane.ERROR_MESSAGE,null,option,option[0]);
        return false;
    }
    return true;
}

Code for isAlreadyWritten():

public static boolean isAlreadyWritten() {
    File f = new File(filename);
    if(f.length()!=0)
    {
        Object[] option = {"Read","Edit"};
        JOptionPane.showOptionDialog(HomePage.getFrame(),"You already updated diary for this day. Do you want to edit?",
                "",JOptionPane.DEFAULT_OPTION,JOptionPane.INFORMATION_MESSAGE,null,option,option[0]);
        return true;
    }
    else
        return false;
}

Code for updateDateChooser():

public static void updateDateChooser(CustomDate date) {
dateChooser.removePropertyChangeListener(lis); //to stop it from getting triggered when date is set programatically
dateChooser.setDate(DateConverter.convertfromCustom(date));
dateChooser.addPropertyChangeListener(lis);
}

Code for updateEditFields():

public static void updateEditFields(CustomDate searchDate, String excontent) {
updateDateChooser(searchDate);
selectedDate = DateConverter.convertDate(dateChooser);
dayinfo.setText("You are editing entry for: "+ new SimpleDateFormat("dd/MM/yyyy").format(dateChooser.getDate()));
contentfield.setText(excontent);

}

Now my dateboundary function is working as expected. whenever a date greater than current date is chosen, the optiondialog gets displayed and its gone after a click, and the datechooser is set to the last selected date, although the propertychange method is called thrice:

  • once before the dialog is displayed
  • twice after the dialog gets closed.

But my isAlreadyWritten() is not working as expected and the optiondialog is getting displayed 4 times with propertychange() method being called four times: once before each time the dialog is displayed.

I want to understand why propertychange is being called 4 times even though the datechooser is detached from the listener when the date is set programatically?

Hari Kiran
  • 188
  • 13
  • The `static` it burns, it burns ... okay, but, you need to remove ALL the `static` declarations and start learning how to share data through your program – MadProgrammer Sep 07 '18 at 07:55
  • [Which date chooser are you using](https://www.google.com/search?client=safari&rls=en&q=JDateChooser&ie=UTF-8&oe=UTF-8) (I can find at least 3) – MadProgrammer Sep 07 '18 at 08:00
  • 1
    Please provide a [mcve] including all the import statements so we can also reproduce your problems. This allows us to debug your code and provide a solution for you. – Sergiy Medvynskyy Sep 07 '18 at 08:01
  • `if(new SimpleDateFormat("dd/MM/yyyy").format(dateChooser.getDate()).equals(new SimpleDateFormat("dd/MM/yyyy").format(currentDate))) {` ... Dates are comparable - besides you should be using `LocalDate` instead – MadProgrammer Sep 07 '18 at 08:06
  • okay thanks @MadProgrammer .. if you can look at the concerning issue of the propertychange method being called multiple times, it would be great. I will implement the suggestions you gave – Hari Kiran Sep 07 '18 at 08:27
  • @HariKiran Until you provide more context to your problem, we can't. As I said, I can find at least 3 different `JDateChooser`s and your code is neither compilable nor runnable, so we simply have no hope – MadProgrammer Sep 07 '18 at 08:29
  • I edited the code. there's only one JDateChooser object which is dateChooser. selecteddate and currentdate are objects of CustomDate class where I defined my own getday(), getmonth(), getyear() functions so that its easier for other purposes. The DateConverter class has two methods which converts JDateChooser date to CustomDate and vice versa @MadProgrammer – Hari Kiran Sep 07 '18 at 08:41
  • 2
    @HariKiran Please, please, check the link I provided earlier, we don't know which library you are using for `JDateChooser`, it is NOT part of the JDK, it is from one of (at least) 3 different libraries, without that information, there is simply no way we can possible diagnose why your `PropertyChangeListener` might be getting called so often. Having said that, you could, instead of removing/adding the listener multiple times, simply set a `ignore` flag which stops the listener itself from doing anything – MadProgrammer Sep 07 '18 at 08:44
  • Oh, I'm sorry. I'm using jcalendar 1.4 [http://www.toedter.com/download/jcalendar-1.4.zip] @MadProgrammer – Hari Kiran Sep 07 '18 at 08:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179621/discussion-between-hari-kiran-and-madprogrammer). – Hari Kiran Sep 07 '18 at 08:52

1 Answers1

2

So, I put together this quick snippet and ran it

import com.toedter.calendar.JDateChooser;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                JDateChooser dateChooser = new JDateChooser();
                dateChooser.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        System.out.println(evt.getPropertyName());
                    }
                });
                dateChooser.setDate(new Date());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(dateChooser);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

}

I opened the date selector and selected a date. The program outputted...

date
ancestor
date
date
  1. Was me setting the date programmatically
  2. ancestor is it getting added to the container
  3. Was me selecting the date picker
  4. Was me selecting a date

So, as you can see, not only are you getting spammed with a lot of "date" property changes, you're also getting all the "other" property changes as well

So, the first thing you want to do, is limit the the notifications to the "date" property only, something like...

dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        System.out.println(evt.getPropertyName());
    }
});

This at least means you don't get bother by all the additional information you don't care about.

While you can add and remove the listener, I tend to find it a pain, as I don't always have a reference to the listener(s), instead, I tend to use a state flag instead

private boolean manualDate = false;
//...
dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (manualDate) {
            return;
        }
        System.out.println(evt.getPropertyName());
    }
});

manualDate = true;
dateChooser.setDate(new Date());
manualDate = false;

Not a big change, but this alone means that you're now down to two event notifications.

Instead, you should compare the oldValue with the newValue of the PropertyChangeEvent

JDateChooser dateChooser = new JDateChooser();
dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (manualDate) {
            return;
        }
        Date newDate = (Date) evt.getNewValue();
        Date oldDate = (Date) evt.getOldValue();
        if (newDate != null && oldDate != null) {
            LocalDate newLD = LocalDate.ofInstant(newDate.toInstant(), ZoneId.systemDefault());
            LocalDate oldLD = LocalDate.ofInstant(oldDate.toInstant(), ZoneId.systemDefault());
            if (newLD.equals(oldLD)) {
                return;
            }
        }
        System.out.println(evt.getPropertyName());
    }
});

And now, we're down to one change event. The only draw back is it won't tell you when they reselect the current date.

A slightly better work flow might be to ignore it all and simply have a JButton that the user can press to perform what ever associated actions you need carried out

Runnable Example...

import com.toedter.calendar.JDateChooser;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    private boolean manualDate;

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                JDateChooser dateChooser = new JDateChooser();
                dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if (manualDate) {
                            return;
                        }
                        Date newDate = (Date) evt.getNewValue();
                        Date oldDate = (Date) evt.getOldValue();
                        if (newDate != null && oldDate != null) {
                            LocalDate newLD = LocalDate.ofInstant(newDate.toInstant(), ZoneId.systemDefault());
                            LocalDate oldLD = LocalDate.ofInstant(oldDate.toInstant(), ZoneId.systemDefault());
                            if (newLD.equals(oldLD)) {
                                return;
                            }
                        }
                        System.out.println(evt.getPropertyName());
                    }
                });
                manualDate = true;
                dateChooser.setDate(new Date());
                manualDate = false;

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(dateChooser);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • yes a simple jbutton would have been better without any of the conflicts, but I was trying to make the user interaction minimal. Thanks for the solution. I could implement by passing the "date" property to the propertychangelistener and using a state flag whenever the date is set programmatically. However I still have two events. Can I know how you identified which event is causing the additional calls? – Hari Kiran Sep 07 '18 at 10:09
  • Observation really. When you press the "select date" button, it triggers another `PropertyChangeEvent`, which is where I suspect you're second event is coming from – MadProgrammer Sep 07 '18 at 10:11