即使在以编程方式设置日期时分离侦听器,JDateChooser 上的 propertychangelistener 也会额外触发 2 次
propertychangelistener on a JDateChooser gets triggered 2 times additionally even when the listener is detached while setting the date programatically
我正在为我正在开发的 java swing 项目使用 JDateChooser,在这个项目中,日期可以通过两种方式设置:由最终用户或以编程方式设置。
所以我在相应的 class 中定义了一个 属性changelistener(变量 trig 被初始化为零并跟踪 属性 的次数改变被倾听)。
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");
}
}
这里是 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;
}
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;
}
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);
}
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);
}
现在我的日期边界函数可以正常工作了。 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 属性change method is called thrice:
- 在显示对话框之前一次
- 对话框关闭后两次。
但是我的 isAlreadyWritten() 没有按预期工作,选项对话框显示了 4 次,属性change() 方法被调用了四次:
每次显示对话框前一次。
我想了解为什么在以编程方式设置日期时,即使日期选择器与侦听器分离,为什么 属性change 仍被调用 4 次?
所以,我把这个简短的片段放在一起 运行 它
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);
}
});
}
}
我打开日期选择器并选择了一个日期。程序输出...
date
ancestor
date
date
- 我是以编程方式设置日期的吗
ancestor
是否添加到容器中
- 我在选择日期选择器吗
- 我在选择日期吗
因此,如您所见,您不仅收到了大量 "date" 属性 更改的垃圾邮件,而且还收到了所有 "other" 属性 也有变化
所以,您要做的第一件事就是将通知限制为 "date" 属性,例如...
dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
}
});
这至少意味着您不会被您不关心的所有其他信息所打扰。
虽然您可以添加和删除侦听器,但我觉得这很麻烦,因为我并不总是有对侦听器的引用,相反,我倾向于使用状态标志
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;
变化不大,但仅此一项就意味着您现在只有两个事件通知。
相反,您应该将 oldValue
与 PropertyChangeEvent
的 newValue
进行比较
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());
}
});
现在,我们只剩下一个更改事件了。唯一的缺点是当他们重新选择当前日期时它不会告诉你。
稍微好一点的工作流程可能是完全忽略它,只需要一个 JButton
,用户可以按下它来执行您需要执行的任何相关操作
可运行示例...
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);
}
});
}
}
我正在为我正在开发的 java swing 项目使用 JDateChooser,在这个项目中,日期可以通过两种方式设置:由最终用户或以编程方式设置。
所以我在相应的 class 中定义了一个 属性changelistener(变量 trig 被初始化为零并跟踪 属性 的次数改变被倾听)。
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");
}
}
这里是 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;
}
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;
}
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);
}
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);
}
现在我的日期边界函数可以正常工作了。 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 属性change method is called thrice:
- 在显示对话框之前一次
- 对话框关闭后两次。
但是我的 isAlreadyWritten() 没有按预期工作,选项对话框显示了 4 次,属性change() 方法被调用了四次: 每次显示对话框前一次。
我想了解为什么在以编程方式设置日期时,即使日期选择器与侦听器分离,为什么 属性change 仍被调用 4 次?
所以,我把这个简短的片段放在一起 运行 它
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);
}
});
}
}
我打开日期选择器并选择了一个日期。程序输出...
date
ancestor
date
date
- 我是以编程方式设置日期的吗
ancestor
是否添加到容器中- 我在选择日期选择器吗
- 我在选择日期吗
因此,如您所见,您不仅收到了大量 "date" 属性 更改的垃圾邮件,而且还收到了所有 "other" 属性 也有变化
所以,您要做的第一件事就是将通知限制为 "date" 属性,例如...
dateChooser.addPropertyChangeListener("date", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
}
});
这至少意味着您不会被您不关心的所有其他信息所打扰。
虽然您可以添加和删除侦听器,但我觉得这很麻烦,因为我并不总是有对侦听器的引用,相反,我倾向于使用状态标志
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;
变化不大,但仅此一项就意味着您现在只有两个事件通知。
相反,您应该将 oldValue
与 PropertyChangeEvent
newValue
进行比较
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());
}
});
现在,我们只剩下一个更改事件了。唯一的缺点是当他们重新选择当前日期时它不会告诉你。
稍微好一点的工作流程可能是完全忽略它,只需要一个 JButton
,用户可以按下它来执行您需要执行的任何相关操作
可运行示例...
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);
}
});
}
}