最终的解决方式采用了额外的线程,鉴于笔者对于多线程的功底不深所以很有可能引起其他问题.待完善.但本博客肯定会帮助你深刻了解问题. 如果你有优雅的多线程处理,别犹豫联系我.
背景:
1.JFace的TableViewer,TreeViewer普遍应用,TextCellEditor,ComboCellEditor,DialogCellEditor等是JFace自带的单元格编辑器. 这些编辑器确实可以满足很多需求,而且使用方便,但随着对于UI交互要求的提高,用户追求更加简单的操所时.这些Editor的编辑操作确实还有一些优化的空间. (毕竟是背景, 说的官方高大上些)
2.这里主要优化的是DialogCellEditor 原因是: 用户想要编辑需要点击两次. 一次点击单元格,一次点击右侧button. 于是有的用户不爽了.(你知道一百个用户第一天得多点击多少次吗 布拉布拉) 用户希望可以直接编辑. 即: 用户点击第一次时,就可以直接在单元格里输入编辑类似TextCellEditor. 如果输入比较复杂. 则再点击Button.在Dialog中编辑. Dialog中可以干很多事. 输入辅助,输入类型提示等.
现状:
这种显而易见的优化,很多人都会想到.我也检索了一些.效果虽然很接近,但是实际上关键问题并没有解决. 迫于公司任务的压力,我也无奈直面了它.花了几天时间,终于实现了效果.虽然不够优雅.
我检索到已有的Demo没有同时实现以下几点:
1.鼠标单击时,有没有把已有的值带到Text的编辑区域里
2.text中的值快捷直接应用.Text的编辑区域 编辑修改后,点击其他空行.(注意:不是其他的有数据的Cell), 新的数值是否保存
3.text中的数据和Dialog时时同步 即: Text的编辑区域 编辑修改后,再单击button,新的未保存的值是否到了Dialog中去,再次修改后,点击OK后,最新的值是否可以保存到Table中.
从实现角度讲,无非是综合TextCellEditor和DialogCellEditor. 如果二者的源代码看懂的差不多.融合应该不难. 最开始我也是这样想的. 但是后来发现jface底层并不那么友善.
定制CellEditor,融合TextCellEditor和DialogCellEditor
实验一:
当我简单的在DialogCellEditor.createContents()中添加了Text控件后,并且在doSetFocus()中让text获取焦点,并为它赋值后,发现需求的第1,3条就满足了.代码如下:
@Override protected Control createContents(final Composite cell) { this.text = new Text(cell, SWT.NONE); this.text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { // TODO Auto-generated method stub if (e.keyCode == 13) { doSetValue(text.getText()); } } public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } }); return this.text; } @Override protected void doSetFocus() { if (text != null) { text.setText(getValue().toString()); text.selectAll(); text.setFocus(); } }
这段代码基本来自TextCellEditor.但由于2号需求不满足会引起很大的问题.
1.保存数值,只能通过打开Dialog点击OK,或者点击其他有数据Cell才能保存
2.如果这个表格只有一行数据.很难保存数据了.
显然无法使用.
我知道你想说什么. 模仿TextCellEditor单击其他空白Cell保存值得逻辑呀.and I did.
实验二:
应用TextCellEditor逻辑.
text.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { TextCellEditor.this.focusLost(); } });
实现很简单.增加失去焦点的相应.当点击其他空白区域时触发.focusLost(). focusLost()逻辑.
protected void focusLost() { if (isActivated()) { <em><strong>fireApplyEditorValue();</strong></em> deactivate(); } }我着重的那行,很清楚. fire监听器应用当前编辑好的值.(TableViewer的是ColumnViewerEditor的匿名内部类.PS:很多Viewer统一实现ICellEditorListener才实现了CellEditor的通用)
以为万事大吉了,想的美,测试的时候,你会发现.2条满足了. 但是第3条却不能满足.先show代码:
this.text.addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { // TODO Auto-generated method stub } public void focusLost(FocusEvent e) { doSetValue(text.getText()); CustomDirectEditDialogCellEditor.this.focusLost(); } });虽然3条不满足,即:如果用户先修改了Text的值,当点击Button后Text的当前值就会作为最终值,所以Dialog中怎么修改也是无用的.
这就是所谓的鱼与熊掌不可兼得. 我本想着修改一两行代码就可以二者兼得了,下一个任务了.但浪费了很多时间后,我意识到这个问题并不是那么容易解决.最后我就踏实的跟源码.究其原因.
关键问题说明:CellEditor只允许赋值一次 即: fireApplyEditorValue();只能被调用一次.
原因:源码写着呢.
/** * Applies the current value and deactivates the currently active cell * editor. */ void applyEditorValue() { <span style="color:#FF0000;">// avoid re-entering</span> <span style="color:#FF0000;"> 为啥 给个原因呀</span> if (!inEditorDeactivation) { try { inEditorDeactivation = true; CellEditor c = this.cellEditor; if (c != null && this.cell != null) { ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent( cell); tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_SAVED; if (editorActivationListener != null && !editorActivationListener.isEmpty()) { Object[] ls = editorActivationListener.getListeners(); for (int i = 0; i < ls.length; i++) { ((ColumnViewerEditorActivationListener) ls[i]) .beforeEditorDeactivated(tmp); } } Item t = (Item) this.cell.getItem(); // don't null out table item -- same item is still selected if (t != null && !t.isDisposed() && c.isValueValid()) { <span style="background-color: rgb(102, 51, 255);">saveEditorValue(c);</span> } <span style="color:#FF0000;">if (!viewer.getControl().isDisposed()) { setEditor(null, null, 0); }</span> <span style="color:#FF0000;">c.removeListener(cellEditorListener); // Control control = c.getControl(); if (control != null && !control.isDisposed()) { if (mouseListener != null) { control.removeMouseListener(mouseListener); // Clear the instance not needed any more mouseListener = null; } if (focusListener != null) { control.removeFocusListener(focusListener); } if (tabeditingListener != null) { control.removeTraverseListener(tabeditingListener); } }</span> c.deactivate(tmp); if (editorActivationListener != null && !editorActivationListener.isEmpty()) { Object[] ls = editorActivationListener.getListeners(); for (int i = 0; i < ls.length; i++) { ((ColumnViewerEditorActivationListener) ls[i]) .afterEditorDeactivated(tmp); } } if( ! this.cell.getItem().isDisposed() ) { this.cell.getItem().removeDisposeListener(disposeListener); } } <span style="color:#FF0000;">this.cellEditor = null; this.cell = null;</span> } finally { inEditorDeactivation = false; } } }
源码我没有详细的读,但是蓝色是赋值重点逻辑. 然后就挥泪斩马谡.红色的代码把底层支持赋值的数据全部移除置空. 我曾试过备份这些数据.再第二次赋值时还原.但太难了 太深入底层了.而且太不符合逻辑.放弃了.
解释上面的两个实验的现象:
实验一:
text和dialog值时时同步. 原因二者都是通过setValue()和getValue()获取和设置值的.
保存值:1.在Dialog点击OK时,应用fireApplyEditorValue();
button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { // Remove the button's focus listener since it's guaranteed // to lose focus when the dialog opens button.removeFocusListener(getButtonFocusListener()); Object newValue =<strong> openDialogBox(editor);</strong> 打开dialog的抽象方法 // Re-add the listener once the dialog closes button.addFocusListener(getButtonFocusListener()); if (newValue != null) { boolean newValidState = isCorrect(newValue); if (newValidState) { markDirty(); doSetValue(newValue); } else { // try to insert the current value into the error message. setErrorMessage(MessageFormat.format(getErrorMessage(), new Object[] { newValue.toString() })); } <strong> fireApplyEditorValue();</strong> } }2.选择其他可编辑的Cell保存时.相应MouseDown的事件 会触发以下逻辑
private void handleMouseDown(MouseEvent e) { ViewerCell cell = getCell(new Point(e.x, e.y)); if (cell != null) { triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent( cell, e)); } }如果点击位置是个正常的Cell 则:getCell不为空. 反之,如果是空白区域则cell为空.
而这决定triggerEditorActivationEvent()是否执行.而这里面有最后调用之前编辑的cell调用fireApplyEditorValue()的逻辑.
void handleEditorActivationEvent(ColumnViewerEditorActivationEvent event) { // Only activate if the event isn't tagged as canceled if (!event.cancel && editorActivationStrategy.isEditorActivationEvent(event)) { <strong>//保护性检测 if (cellEditor != null) { //如果上一个CellEditor还存在(cellEditor=null,实在ApplyApplyEditorValue()中执行的,我 applyEditorValue(); //展示过标记为红色. 所以如果cellEditor不为空则意味着上一个cell的值未应用,所以触发应用 }</strong> this.cell = (ViewerCell) event.getSource(); //激活新的Cell的相关编辑 // Only null if we are not in a deactivation process see bug 260892 if( ! activateCellEditor(event) && ! inEditorDeactivation ) { this.cell = null; this.cellEditor = null; } } }
这也就是点击空白cell的不能保存,可编辑cell能保存的原因.
实验二:
点击空白可以保存.
原因:由于text先失去焦点,触发了fireApplyEditorValue()的逻辑新的值生效. 所以不需要通过上面保护性检测来触发保存
dialog中的新值赋值无效
原因: 点击button打开dialog时,text已经失去焦点之后的事了.. 所以dialog关闭后,想再次应用fireApplyEditorValue() 已经失去了条件.即: 第二次调用无效 所以注定值不能保存.
问题解决:
综合一下时在实验二的基础上解决容易些. 而且避免修改jface的深层逻辑.
解决思想:
所以我从避免两次调用fireApplyEditorValue()触发
如果text失去焦点后,但没有打开dialog就出发fireApplyEditorValue(),
如果text失去焦点后,但打开了dialog就不触发fireApplyEditorValue();
但text如何知道未来发生的事进行不同的处理呢. 让它等待一小会再检测
将focusLost()修改为:
@Override public void focusLost(FocusEvent e) { UIJob job = new UIJob("输入域保存值异常") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (dialog == null) { NormalCellEditor.this.focusLost(); //这是我自定义的类名 } return Status.OK_STATUS; } }; job.schedule(200); <strong>//延迟200ms 执行run()</strong> };
虽然这样会使点击空白区域保存慢一点,但还是可以接受的. 还有些小细节 :add和remove这个focusListener 我就不贴代码了.
思路分析清楚了,不同的环境可能用不同的应用线程的方式,这个很明显不是最好的解决方式,如果有更好的解决方法,希望及时和我探讨 一起提升.
最后吐槽下,jface为啥不允许赋值两次,为啥,为啥.