目录
目录README.md

Lab3-15307110273

一、用户手册

按日期添加

先按照日期添加一个全天类事件,这里我们选择2018年4月22号。

image-20180422103616472

点击添加,出现如下窗口

image-20180422104223582

输入事件信息,如果没有输入,将无法点击确定完成添加,这里我们输入“生日”,点击确定,添加完成。添加完成后,在界面上会发生变化,我们将在日期查询的时候说明。

按日期查询

添加完成后,我们会看到如下界面,被高亮的日期,以及被添加的事件

image-20180422111143749

image-20180422104601174

这是因为添加事件成功过后,程序自动进行了对应添加日期的查询工作,也就是说上述事件是由查询按钮触发的,回到第一张图的查询按钮

image-20180422103616472

如果手动点击查询,就会出现上图中的查询结果信息。

按时间段添加

image-20180422105434830

左边为开始时间,右边为结束时间,如果选定的开始时间大于结束时间会出现下面的结果

image-20180422105733901

若合法,出现下图

image-20180422105905477

输入”出差”,点击确定,即添加完成。

按时间段查询

上述添加完成后,程序会自动完成对应时间段的查询工作(触发时间段的查询按钮),如图所示

image-20180422110147165

若我们手动点击时间段对应的查询按钮,也会出现上述情况。我们发现,所有在查询时间段内的日期类日程和时间段日程都显示出来了。

删除

点击日程栏里对应日程的删除按钮,即完成了日程的删除。这里我们删除生日事件,删除过后程序会自动帮我们刷新查询(重复查询之前的时间段或日程),出现如下界面

image-20180422110819525

异常输入处理

若输入的非法日期,程序不接受该日期,而是将该日期转化为最近一次输入的合法日期(至于为什么这样做,见“感悟”)。即若输入日期不合法,程序会根据您最近一次在该输入框输入的合法的日期进行增添或查找。日期的合法格式为“yyyy-MM-dd”,与lab2相同,带时间的日期合法格式为”yyyy-MM-dd HH:mm:ss”,其中年份必须为4位数,月日时分秒必须为2位数,这与日期的合法性检测不同。

在按时间段添加的功能中,若开始时间大于结束时间,会弹出提示框阻止用户添加。

在按时间段查询的功能中,若开始时间大于结束时间,会弹出提示框阻止用户查询。

新增功能及注意事项

查询与添加用的是相同的日期控件,这是为了使页面更简洁。

本项目异常处理机制与lab2不太相同,做了一些优化,详情见上一节”异常输入处理”

日历中的日期是可以点击的,点击过后会出现蓝色边框,增强与用户的交互感。

添加了类库apache-common-logging用于打印错误日志

添加了类库tornadofx-controls用于LocalDateTime类型数据控件

用css对界面渲染

至此恭喜您已成功学会使用日历的所有功能,撒花

二、记录事项Memo类的设计

public class Memo {
    /* 用于存储日期类的Memo */
    private static LinkedList<Memo> dayMemoList = new LinkedList<>();
    /* 用于存储时间段类的Memo */
    private static LinkedList<Memo> timeIntervalMemoList = new LinkedList<>();
    /* 时间段类的Memo的开始时间和结束时间, 日期类的Memo这两个属性值为null */
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    /* 日期类的Memo的日期, 时间段类的Memo这个属性值为null */
    private LocalDate localDate;
    /* 该memo是日期类的还是时间段类的 */
    private boolean isInterval;
    /* 该memo的内容 */
    private String content;
    /* ... */
}

构造两种不同的memo

/* 构造时间段类Memo */
public Memo(LocalDateTime startDate, LocalDateTime endDate, String content)
/* 构造日期类Memo*/
public Memo(LocalDate date, String content)

添加memo

/* 根据memo实例的类型添加到对应的list实现存储 */
public void add() {
    if (isInterval) {
        timeIntervalMemoList.add(this);
    } else {
        dayMemoList.add(this);
    }
}

删除memo

/* 将memo实例根据其类型从对应的list中删除, 成功返回true, 若不存在则返回false */
public boolean remove() {
    if (isInterval) {
        return timeIntervalMemoList.remove(this);
    } else {
        return dayMemoList.remove(this);
    }
}

memo的4种查询方式

/* 根据给定日期查询当前日期的日期类的memo, 若传入参数出现null则返回null */
public static LinkedList<Memo> queryFromDayMemoList(LocalDate localDate) {
    if (localDate == null) {
        return null;
    }
    LinkedList<Memo> resultList = new LinkedList<>();
    for (Memo memo: dayMemoList) {
        if (memo.localDate.isEqual(localDate)) {
            resultList.add(memo);
        }
    }
    return resultList;
}

/* 根据给定日期查询所有包含该日期的时间段类memo,若传入参数出现null则返回null */
public static LinkedList<Memo> queryFromTimeIntervalMemoList(LocalDate localDate) {
    if (localDate == null) {
        return null;
    }
    return queryFromTimeIntervalMemoList(localDate.atStartOfDay(), localDate.atTime(23,59,59));
}

/* 根据给定时间段查询所有在该时间段内的日期类memo,若传入参数出现null则返回null */
public static LinkedList<Memo> queryFromDayMemoList(LocalDateTime startDate,LocalDateTime endDate) {
    if (startDate == null || endDate == null) {
        return null;
    }
    LinkedList<Memo> resultList = new LinkedList<>();
    for (Memo memo: dayMemoList) {
        if ((memo.localDate.isAfter(startDate.toLocalDate()) || memo.localDate.isEqual(startDate.toLocalDate())) &&
                (memo.localDate.isBefore(endDate.toLocalDate()) || memo.localDate.isEqual(endDate.toLocalDate()))) {
            resultList.add(memo);
        }
    }
    return resultList;
}

/* 根据给定时间段查询所有与该时间段存在相交区间的时间段类memo,若传入参数出现null则返回null */
public static LinkedList<Memo> queryFromTimeIntervalMemoList(LocalDateTime startDate,LocalDateTime endDate) {
    if (startDate == null || endDate == null) {
        return null;
    }
    LinkedList<Memo> resultList = new LinkedList<>();
    for (Memo memo: timeIntervalMemoList) {
        if ((memo.startDate.isBefore(endDate) || memo.startDate.isEqual(endDate)) &&
                (memo.endDate.isAfter(startDate) || memo.endDate.isEqual(startDate))) {
            resultList.add(memo);
        }
    }
    return resultList;
}

三、FormatterUtil的设计

FormatterUtil类用于检测用户输入的字符串是否能转化为日期或带时间的日期

package com.zxy;
import java.time.LocalDateTime;
import java.time.format.*;
public class FormatterUtil {
       private FormatterUtil() {
        throw new IllegalAccessError("Utility class");
    }

    /**
     * Judge whether the input dateString is formatted and valid. For example, 2018-2-31 is not valid
     * @param dateString the input dateString
     * @return true if the dateString is formatted and valid, false if the date is not.
     */
    public static boolean isValidDate(String dateString) {
        try {
            CalendarDate date = new CalendarDate(dateString);
            return DateUtil.isValid(date);
        } catch (MyException e) {
            return false;
        }
    }

    /**
     * Judge whether the input dateTimeString is formatted and valid. For example, 2018-2-17 24:00:00 is not valid
     * @param dateTimeString the input dateTimeString
     * @return true if the dateTimeString is formatted and valid, false if the date is not.
     */
    public static boolean isValidDateTime(String dateTimeString) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss").withResolverStyle(ResolverStyle.STRICT);
        try {
            LocalDateTime date = LocalDateTime.parse(dateTimeString, formatter);
            return true;
        } catch (DateTimeParseException e) {
            return false;
        }
    }

}

四、感悟

在UI设计方面,在界面的设计感觉是个最费时的工作,我觉得javafx不太好用,不太好进行布局以及一些交互,渲染我们可以css做,就比较简单。用而且控件类不太完善,比如datepicker用于localdate类型的输入,但对于localdatetime类就没有相应的控件了,网上参考了一些控件类比如jfxrt-control等,结合各种原因综合考虑了下还是选择了tornadofx-control。整个页面的交互性还是比较强的,做到了动态添加动态查询动态删除。

在异常处理方面,保留了datepicker本身对输入异常的处理方式。通过对datepicker类的源码研究,发现这个控件有一个自动恢复机制,用了一个lastvaliddate属性来保留上一次输入的合法日期,在判断输入日期不合法时,datepicker不会改变其value,若输入合法,datepicker才会改变。与lab2的错误抛出让用户处理的机制相比,我觉得datepicker这个内置的处理方式更好,因为他完成了一个合法的转换过程,是比较贴近用户实际需要的。所以我保留了datepicker的这个方法(允许程序对非法输入进行自行恢复,而不是交由用户处理),而且datepicker选择日期是比较方便的。为了可扩展性(假设需要用户手动处理而不是内部转化),我也写了一个FormatterUtil类,他用来判断字符串能不能转换为日期和带时间的日期。如果不用datepicker而用普通的textfield的话,那么这个类将帮助我们实现与lab2相似的功能(即程序将不合法的日期输入问题交由用户来处理,而不是进行内部处理)。

在空事件方面,我们拒绝事项没有任何内容,我们在添加的界面中避免了没有内容事件的添加。当内容为空时,我们无法点击确定按钮。这个问题如果从后端逻辑来考虑的话,会比较复杂,而且和界面交互还很麻烦,有时候换一种思维,如果我们在界面完成这样的功能,就把这个问题完美解决了。

在Memo设计方面,对于全天事件和时间段事件的一起处理比较麻烦,最开始考虑的是一起处理,写在一个日期和带时间的日期用Date类可以一并处理。后来在实现的过程中发现,还是分开用两个类处理比较好(LocalDate和LocalDateTime),这样程序逻辑更清晰一点,而且易于和前端交互。

邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

©Copyright 2023 CCF 开源发展委员会
Powered by Trustie& IntelliDE 京ICP备13000930号