解决日期选择问题,一劳永逸(使用Decorator模式实现日期选择组件)(三)
基本的selector接口
日期选择类的核心是Date_selector_panel.仔细分析后,首先做了一些如下变量声明,不要被"常量初始化"语法掷出。(以为括号中的代码是静态的,和其他没有任何联系.)实例初始化声名使用this的结构避免了在构造器中连接另一个构造器。他们提供了保证在每个构造器都能有效调用的初始化方法。同时在一个地方给出所有的初始化代码也是非常方便的。散布在各个地方的没有初始化的变量很容易引起运行错误。当移动代码到构造器中也是非常困难的,因为移动过程中你必须考虑你已经改变的2个地方。比如说,在下面日历声明中代码就是实例初始化:
public class Date_selector_panel extends JPanel implements Date_selector
{
//这些字符串应该应该在资源包中,因此它们是国际化的。
private String[] months =
{ "Jan","Feb", "Mar","Apr", "May","June",
"July","Aug","Sept","Oct","Nov","Dec"
};
private static final int DAYS_IN_WEEK = 7, // 一个 星期的天数
MAX_WEEKS = 6; // 在一月中最大的//星期数。
// 用户选择的日期
private Date selected = null;
private Calendar calendar = Calendar.getInstance();
{ calendar.set( Calendar.HOUR, 0 );
calendar.set( Calendar.MINUTE, 0 );
calendar.set( Calendar.SECOND, 0 );
}
//显示在屏幕上的当前日期
private final Calendar today = Calendar.getInstance();
下面便开始处理点击不同天的事件。我采用比较容易实现的外观即将每天都以一个标签是日期的按纽显示在屏幕上,我定义了一个简单的监听对象并加载到每个按纽中。由监听对象获取按纽标签值,由标签值可以得到日期与日历的位置,然后它将从日历中获取日期对象并且将日期字符串发送给监听器(通过调用fire_ActionEvent()的方式)。
//一个监听器适合所有calendar事件private final Button_handler day_listener = new Button_handler();private class Button_handler implements ActionListener{ public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("D")) { String text = ((JButton) e.getSource()).getText(); if(text.length() > 0) // <=0 means click on blank square. Ignore. { calendar.set ( calendar.get(Calendar.YEAR), // Reset the calendar calendar.get(Calendar.MONTH), // to be the chosen Integer.parseInt(text) // date. ); selected = calendar.getTime(); fire_ActionEvent( SELECT_ACTION, selected.toString() ); } } }}
JAVA的AWTEventMulticaster 类实现了动作监听。这种实现在我提供的文档(一篇关于multicasters的文章列在 Resources <http://www.javaworld.com/javaworld/jw-07-2003/>.)中叙述的比较清楚。
private ActionListener subscribers = null;public synchronized void addActionListener(ActionListener l){ subscribers = AWTEventMulticaster.add(subscribers, l);}public synchronized void removeActionListener(ActionListener l){ subscribers = AWTEventMulticaster.remove(subscribers, l);}private void fire_ActionEvent( int id, String command ){ if (subscribers != null) subscribers.actionPerformed(new ActionEvent(this, id, command) );}
//在面板显示前调用addNotify()。它实际上创建了基本的图象对象,因此你可以调用super.addNotify()去调用面板。
//这里使用它是一个入口去发送初始化ActionEvent到支持标题的任何实体。public void addNotify(){ super.addNotify(); int month = calendar.get(Calendar.MONTH); int year = calendar.get(Calendar.YEAR); fire_ActionEvent( CHANGE_ACTION, months[month] " " year );}
然后我创建并初始化了代表天的按纽数组。有趣的是,日历并不能用二维数组表示,于是,我把按纽放在GridLayout布局中,让布局管理器来获取他的状态。在线形数组中在Grid中的第一个按钮表示第一周的第一天;第八个按纽是第二周的第一天;等等。下面是代码 :
--"); days = day; day.setBorder (BorderFactory.createEmptyBorder(1,2,1,2)); day.setFocusPainted (false); // Cannot get focus day.setActionCommand ("D"); day.addActionListener (day_listener);// Our single listener day.setOpaque (false); // Transparent background }}
然后便是构造器。主要工作都在这没有参数的构造器中。它创建了包含日历的面板,建立了gridlayout布局,并将按纽加入到布局中:
public Date_selector_panel()
{
JPanel calendar_display = new JPanel();
calendar_display.setOpaque(false);
calendar_display.setBorder( BorderFactory.createEmptyBorder(5,3,0,1) );
calendar_display.setLayout(new GridLayout(MAX_WEEKS /*rows*/, DAYS_IN_WEEK /*columns*/ ));
for( int i = 0; i < days.length; i )
calendar_display.add(days);
setOpaque( false );
setLayout( new BorderLayout() );
add(calendar_display, BorderLayout.CENTER);
update_calendar_display();
}
全部代码清单还含有少量的的方便组件,使用他们比用当前日期能更好的初始化日历。在这里我没有谈到。
在Date_selector_panel中最值得说的是update_calendar_display()方法,当改变日历的时候该方法更新显示的日历。我使用java.util.Calendar()方法来判断星期日与月初的偏移量并在这些按钮上设置空字符串的标签。最后我将用空字符串来填写表示月末的按钮标签。
通过这种方式,你看到的每个按都在改变,即使它代表的是当前月中的无效天。这里并不需要用代码实现 ,因为当你 将按钮放入GridLayout中,布局便会自动列出你放入的按钮。
private void update_calendar_display(){ setVisible(false); // Improves paint speed and reduces flicker. clear_highlight(); // The buttons that comprise the calendar are in a single // dimensioned array that was added to a 6x7 grid layout in // order. Because of the linear structure, it's easy to // lay out the calendar just by changing the labels on // the buttons. Here's the algorithm used below: // // 1) Find out the offset to the first day of the month. // 2) Clear everything up to that offset. // 3) Add the days of the month. // 4) Clear everything else. int month = calendar.get(Calendar.MONTH); int year = calendar.get(Calendar.YEAR); fire_ActionEvent( CHANGE_ACTION, months[month] " " year ); calendar.set( year, month, 1 ); // First day of the current month. int first_day_offset = calendar.get(Calendar.DAY_OF_WEEK); /* 1 */ assert Calendar.SUNDAY == 0; assert first_day_offset < days.length; int i = 0; while( i < first_day_offset-1 ) /* 2 */ days[i ].setText(""); int day_of_month = 1; for(; i < days.length; i ) /* 3 */ { if( calendar.get(Calendar.MONTH)==today.get(Calendar.MONTH)