• TableViewer的单元格中编辑日期


     

    单元格编辑器的需求出现

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

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

    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所示,分为三个部分——一个编辑框、一个按钮、一个画有日历的面板

    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官方的源代码,仍然是学习的重要手段,加上多看示例代码,这两种方法可以大大减少重复劳动。

  • 相关阅读:
    Pycharm中 import 引入同级文件失败问题
    Python实现 K_Means聚类算法
    Python 的 Matplotlib 画图库
    Numpy库应用实例——GPS定位
    Python 的 Numpy 库
    Python 各种库的安装
    Python 的 pandas 实践
    方差、协方差、协方差矩阵
    Python实现梯度下降法
    Vue 不睡觉教程3
  • 原文地址:https://www.cnblogs.com/huqingyu/p/1156428.html
Copyright © 2020-2023  润新知