TableViewer的单元格中编辑日期

时间:2023-01-18 12:55:50

 

单元格编辑器的需求出现

最近我需要自己做一个小的应用程序,使用SWT来设计界面,其中需要用到JFaceTableViewer,在使用之后,发现它比SwingJTable更好用,而且在向表格的单元格中加入编辑器的时候,更加方便、容易(也有可能是我对SwingJTable中的单元格编辑器研究的不够造成的错觉……)

我需要向TableViewer生成的对象的单元格中加入编辑日期的编辑器(如图1所示)

TableViewer的单元格中编辑日期

1 单元格中的日期编辑器

在网上利用关键字“JFace CellEditor”等等组合,可以容易地搜索到很多关于JFace界面组件中CellEditor这个类的覆写方法,但是,我查找了几天,也只是找到了一些对我没有用处的文章。

82991,用了四天的时候,终于很好的解决了这个编辑器的问题,所以现在用代码来小结一下收获:

自定义的日期单元格编辑器的代码

实现这个单元格编辑器的代码如下:

注:请快速浏览代码之后,详细的了解后面关于代码的解释

/**

 * Updated at 2007-8-30 下午04:31:44

 * <p>

 *

 * @author 郭凯

 *

 */

public class DatepickerCellEditor extends CellEditor {

       public static Logger log = Logger.getLogger(DatepickerCellEditor.class);

       private DatePickerCombo datepickerWidget;

       protected Date selectedDate;

       public DatepickerCellEditor() {

       }

       public DatepickerCellEditor(Composite parent, int style) {

              super(parent, style);

       }

       protected Control createControl(Composite parent) {

              datepickerWidget = new DatePickerCombo(parent, getStyle());

              datepickerWidget.getDp().addSelectionListener(new SelectionAdapter() {

                     public void widgetDefaultSelected(SelectionEvent event) {

                            applyEditorValueAndDeactivate();

                     }

                     public void widgetSelected(SelectionEvent event) {

                            selectedDate = datepickerWidget.getDate();

                     }

              });

              datepickerWidget.addTraverseListener(new TraverseListener() {

                     public void keyTraversed(TraverseEvent e) {

                            if (e.detail == SWT.TRAVERSE_ESCAPE

                                          || e.detail == SWT.TRAVERSE_RETURN) {

                                   e.doit = false;

                            }

                     }

              });

              return datepickerWidget;

       }

       protected void applyEditorValueAndDeactivate() {

              selectedDate = datepickerWidget.getDate();

              markDirty();

              fireApplyEditorValue();

              deactivate();

       }

       protected Object doGetValue() {

              focusLost(); // 在获取该单元格编辑器的值的时候,也应该令该单元格编辑器失去焦点

              return Constants.format(selectedDate);

       }

       protected void doSetFocus() {

              datepickerWidget.getDp().setFocus();

       }

       protected void doSetValue(Object value) {

              Assert.isTrue(datepickerWidget != null && (value instanceof String));

              selectedDate = Constants.parse((String) value);

              datepickerWidget.setDate(selectedDate);

       }

       protected void focusLost() {

              System.out.println("focusLost");

              if (isActivated()) {

                     applyEditorValueAndDeactivate();

              }

       }

       public Date getSelectedDate() {

              return datepickerWidget.getDate();

       }

       public void setSelectedDate(Date date) {

              datepickerWidget.setDate(date);

       }

}

首先,得关注几个与其他CellEditor的实现不一样的地方

1.       createControl()方法中,我们没有对datepickerCombo或者datepickerCombo中的属性dp添加焦点监听器(代码中addFocusListener这部分被注释掉了)。

2.       doGetValue()方法中,我们让当前的CellEditor失去了焦点(这里可以参考ComboBoxCellEditor的代码,不难发现它的doGetValue()方法是不处理焦点的)

这段代码依旧不完美,因为doGetValue()方法中如果让CellEditor失去焦点的话,还会出现问题——因为doGetValue()调用的时机是在CellEditor的值发生改变的时候,即是说每次我们打开日期CellEditor,修改一次日期,就会丢失该单元格编辑器的焦点,那样的结果使得用户不得不点击一下其他的单元格,再点回到这个单元格,重新选择其他日期(假定他第一次没有选对的话)。这就意味着这里的实现很ugly

该怎么办呢?重新分析~~

我们的最初的问题是这样的

来自http://sourceforge.net/projects/swt-datepickerSWT的日期控件是相当不错的,当然自己用的时候,还是需要修改相关代码。

该控件如图2所示,分为三个部分——一个编辑框、一个按钮、一个画有日历的面板

TableViewer的单元格中编辑日期

2 SWT-Datepicker控件截图

根据源代码,我们了解到他们各自的事件控制分别在textEvent()arrowEvent()dpEvent(),辅助的事件控制在popupEvent()comboEvent()之中

直接使用没有修改过的DatepickerCombo到我们的DatepickerCellEditor当中的时候,会出现焦点无法捕获的问题。我们在DatepickerCombo类的构造器中,可以找到一段定义Listener接口对象的代码

              Listener listener = new Listener() {

                     public void handleEvent(Event event) {

                            log.info(event);

                            if (popup == event.widget) {

                                   popupEvent(event);

                                   return;

                            }

                            ……

                     }

       };

我们在handleEVent()方法的第一行加上log,而后看看这些事件信息。

结果使用未修改的代码的时候,我们看到当日历面板被箭头按钮弹出的时候,它获得焦点后马上就丢失了焦点。

………………………………(这里省略了思路出现问题绕弯子的过程)

最终我们关注到了一个已有的实现类ComboBoxCellEditor类的代码,发现这个类里面使用的是CCombo,而CCombo的代码与DatepickerCombo之间存在着惊人的相似。可是在使用ComboBoxCellEditor的时候却不会出现焦点方面的问题。

仔细查看了代码之后,发现CComboDatepickerCombo最大的不同就在于焦点处理的代码:

DatepickerCombo类是将FocusInFocusOut两个事件的代码放到textEvent()dpEvent()arrowEvent()等几个事件处理方法之中,而CCombo则将焦点处理集中交给handleFocus()方法,其代码如下:

void handleFocus (int type) {

       if (isDisposed ()) return;

       switch (type) {

              case SWT.FocusIn: {

                     if (hasFocus) return;

                     if (getEditable ()) text.selectAll ();

                     hasFocus = true;

                     Shell shell = getShell ();

                     shell.removeListener (SWT.Deactivate, listener);

                     shell.addListener (SWT.Deactivate, listener);

                     Display display = getDisplay ();

                     display.removeFilter (SWT.FocusIn, filter);

                     display.addFilter (SWT.FocusIn, filter);

                     Event e = new Event ();

                     notifyListeners (SWT.FocusIn, e);

                     break;

              }

              case SWT.FocusOut: {

                     if (!hasFocus) return;

                     Control focusControl = getDisplay ().getFocusControl ();

                     if (focusControl == arrow || focusControl == list || focusControl == text) return;

                     hasFocus = false;

                     Shell shell = getShell ();

                     shell.removeListener(SWT.Deactivate, listener);

                     Display display = getDisplay ();

                     display.removeFilter (SWT.FocusIn, filter);

                     Event e = new Event ();

                     notifyListeners (SWT.FocusOut, e);

                     break;

              }

       }

}

       关键代码以蓝低白字显示。而CCombo类中的一个重要的属性对象的filter的声明代码则放在CCombo的构造器中,其代码如下:

       filter = new Listener() {

              public void handleEvent(Event event) {

                     Shell shell = ((Control)event.widget).getShell ();

                     if (shell == CCombo.this.getShell ()) {

                            handleFocus (SWT.FocusOut);

                     }

              }

       };

这个监听器对象意味着,shell的任何响应事件将会引发FocusOut事件处理代码的执行,这也使得FocusInFocusOut事件处理代码中都应该及时的清除filter监听器,否则可能导致GUI直接当在事件的响应死循环中。(另外我们可以看到SWT文档中声明了filter的对性能可能造成的影响,所以在使用这种可能影响性能的工具的时候,还是应该多多参考Eclipse附带的源代码)

最终DatepickerCellEditor类的源代码为:

/**

 * Updated at 2007-8-30 下午04:31:44

 * <p>

 *

 * @author 郭凯

 *

 */

public class DatepickerCellEditor extends CellEditor {

       public static Logger log = Logger.getLogger(DatepickerCellEditor.class);

       private DatePickerCombo datepickerWidget;

       protected Date selectedDate;

       public DatepickerCellEditor() {

       }

       public DatepickerCellEditor(Composite parent, int style) {

              super(parent, style);

       }

       protected Control createControl(Composite parent) {

              datepickerWidget = new DatePickerCombo(parent, getStyle());

              datepickerWidget.getDp().addSelectionListener(new SelectionAdapter() {

                     public void widgetDefaultSelected(SelectionEvent event) {

                            applyEditorValueAndDeactivate();

                     }

                     public void widgetSelected(SelectionEvent event) {

                            selectedDate = datepickerWidget.getDate();

                     }

              });

              datepickerWidget.addTraverseListener(new TraverseListener() {

                     public void keyTraversed(TraverseEvent e) {

                            if (e.detail == SWT.TRAVERSE_ESCAPE

                                          || e.detail == SWT.TRAVERSE_RETURN) {

                                   e.doit = false;

                            }

                     }

              });

              datepickerWidget.addFocusListener(new FocusAdapter() {

                     public void focusLost(FocusEvent e) {

                            log.info("datepickerWidget focus lost caused by :\n" + e);

                            DatepickerCellEditor.this.focusLost();

                     }

              });

              return datepickerWidget;

       }

       protected void applyEditorValueAndDeactivate() {

              selectedDate = datepickerWidget.getDate();

              markDirty();

              fireApplyEditorValue();

              deactivate();

       }

       protected Object doGetValue() {

              return Constants.format(selectedDate);

       }

       protected void doSetFocus() {

              datepickerWidget.setFocus();

       }

       protected void doSetValue(Object value) {

              Assert.isTrue(datepickerWidget != null && (value instanceof String));

              selectedDate = Constants.parse((String) value);

              datepickerWidget.setDate(selectedDate);

       }

……

}

小结:参考Eclipse官方的源代码,仍然是学习的重要手段,加上多看示例代码,这两种方法可以大大减少重复劳动。