为了保障原创作者在本站发表文章的利益, 并维护本站原创的精神, 特声明: RIAShanghai对有以下任何情况之一的文章将不通知作者并直接进行快意删除:
- 非原创, 或者原创但一文多发;
- 各种形式的广告与吹擂;
- 不符合本站文章格式.
欢迎各位读者监督. 谢谢合作. 另: 作为Adobe正式的UG, 我们将把Adobe不定期分发的软件,书籍及各种纪念品赠送给发文活跃的作者, 共同进步.
Pattern name 模式名: Cancellable Selection Classification 分类: Behavioral
Motivation: 在多对象共享同一个编辑器(editor)或者在任何时间仅允许一个编辑器的情况下, 如果当前对象尚未保存, 界面不应该直接转换到新的对象 - 至少应该给用户警告并提示选择的机会. 例如: 多个tabs, 每个tab页面有可编辑的对象; 一个tree, table或者list上显示所有文件, 旁边为唯一共享的编辑器.
Approach:
将selection明确分成两个阶段 - 一是试图选择的阶段, 此阶段的任务是询问所有listeners是否同意选择; 如果第一阶段一致同意, 第二阶段才是实施真正的选择.
Implementation (Flex):
import mx.controls.Tree;
import mx.core.EventPriority;
import mx.events.ListEvent;
/**
* Dispatched when a selecting is attempted and also when it is confirmed.
* @eventType com.insprise.common.ui.EventCancellableSelection.EVENT_EVENT_CANCELLABLE_SELECTION
*/
[Event(name="eventCancellableSelection", type="com.insprise.common.ui.EventCancellableSelection")]
/**
* A tree that supports cancellable node selection.
* You should use property currentItem to retrieve the confirmed selected item.
* Events:
* add listeners to EventCancellableSelection to cancel default or monitor initial selecting when its kind is SELECTING;
* and retrieve current value when its kind is SELECTED.
*/
public class TreeCancellableSelect extends Tree {
protected var _currentItem:*;
// Constructor.
public function TreeCancellableSelect() {
super();
addEventListener(ListEvent.CHANGE, onTreeSelChange);
addEventListener(EventCancellableSelection.EVENT_CANCELLABLE_SELECTION, handleNodeSelecting, false, EventPriority.DEFAULT_HANDLER); // low priority
// Ref: "Creating Default, Cancelable Event Handlers" - http://www.darronschall.com/weblog/2008/01/creating-default-cancelable-e...
}
// Called when user selected a OU from the tree.
protected function onTreeSelChange(event:ListEvent):void {
dispatchEvent(new EventCancellableSelection(true, EventCancellableSelection.KIND_SELECTING, selectedItem, _currentItem)); // cancellable.
}
// Called when EventCancellableSelection.KIND_SELECTING and all other handlers have already been invoked to check if action cancelled.
protected function handleNodeSelecting(event:EventCancellableSelection):void {
if(! event.isKindSelecting()) { // only handle selecting
return;
}
if(event.isDefaultPrevented()) { // cancelled.
selectedItem = _currentItem; // restore
}else{ // ok
doSetCurrentItem(selectedItem);
dispatchEvent(new EventCancellableSelection(false, EventCancellableSelection.KIND_SELECTED, _currentItem, _currentItem));
}
}
/**
* The current selected item (confirmed).
* When set, it dispatches cancellable EVENT_ITEM_SELECTING event; if set is done, it dispatches EVENT_ITEM_SELECTED event.
*/
public function get currentItem():* {
return _currentItem;
}
public function set currentItem(value:*):void {
selectedItem = value;
onTreeSelChange(null);
}
/**
* Directly set the current item.
* A subclass may override this method to handle actual selection - remember to call super.doSetCurrentItem in the first line.
*/
protected function doSetCurrentItem(value:*):void {
this._currentItem = value;
}
} // End class
import flash.events.Event;
/**
* Repesenting a two staging selection with first stage cancellable: stage 1: selecting; stage 2: selected (if stage 1 is not cancelled).
*/
public class EventCancellableSelection extends Event
{
/** Repesenting a two staging selection with first stage cancellable: stage 1: selecting; stage 2: selected (if stage 1 is not cancelled). */
public static var EVENT_CANCELLABLE_SELECTION:String = "eventCancellableSelection";
/** Event dispatched when an selecting attempt is made; cancellable */
public static var KIND_SELECTING:String = "eventSelecting";
/** Event dispatched when selection is confirmed. */
public static var KIND_SELECTED:String = "eventSelected";
/** Either of KIND_SELECTING or KIND_SELECTED */
public var kind:String;
/** The attempted selection */
public var selectingValue:*;
/** The existing value - previously selected value. */
public var existingValue:*;
/** Constructor */
public function EventCancellableSelection(cancelable:Boolean, kind_:String, selectingValue_:*, existingValue_:* = null) {
super(EVENT_CANCELLABLE_SELECTION, bubbles, cancelable);
if(kind_ != KIND_SELECTING && kind_ != KIND_SELECTED) {
throw new UnsupportedError(kind_);
}
this.kind = kind_;
this.selectingValue = selectingValue_;
this.existingValue = existingValue_;
if(kind_ == KIND_SELECTING && !cancelable) {
throw new Error("KIND_SELECTING should be cancelable. ");
}
if(kind_ == KIND_SELECTED && cancelable) {
throw new Error("KIND_SELECTED should not be cancelable. ");
}
}
public function isKindSelecting():Boolean {
return kind == KIND_SELECTING;
}
public function isKindSelected():Boolean {
return kind == KIND_SELECTED;
}
override public function clone():Event {
return new EventCancellableSelection(cancelable, kind, selectingValue, existingValue);
}
override public function toString():String {
return formatToString("EventCancellableSelection", "kind", "selectingValue", "existingValue");
}
} // End class
// Usage:
var tree:TreeCancellableSelect = ...;
tree.addEventListener(EventCancellableSelection.EVENT_CANCELLABLE_SELECTION, onTreeSelection);
/**
* Called when node selection on the tree is attempted or confirmed.
*/
protected function onTreeSelection(event:EventCancellableSelection):void {
if(event.isKindSelecting()) { // attempting.
if(editor.isDirty()) {
// stop the default action and warn the user.
event.preventDefault();
Alert.show("Discard unsaved changes? ", "Warn", Alert.YES|Alert.CANCEL, null, onWarnUnsavedChangesAlertClose).data = event;
}
}else{ // selection changed.
if(event.selectingValue === event.existingValue) { // same, do not care.
return;
}
var myObject:* = event.selectingValue as OrgUnit;
editor.setModel(myObject);
}
}
// 为保存警告关闭响应
protected function onWarnUnsavedChangesAlertClose(e:CloseEvent):void {
if(e.detail != Alert.YES) { // cancel selecting on tree
tree.currentItem = EventCancellableSelection(Alert(e.target).data).existingValue;
return;
}else { // proceeds selections and discard unsaved changes
ouTree.currentItem = EventCancellableSelection(Alert(e.target).data).selectingValue; // will trigger EventCancellableSelection
}
}
Flex's Event.preventDefault()可参考: "Creating Default, Cancelable Event Handlers".
Implementation (Java):
Java实现更为简单, 跟Flex使用特别的asynchronized event dispatching不同, Java就是一般的同线程代码运行. 有两种方法, 一是直接用代码for each listener, call listener.method检查是否每个赞同, 然后再考虑下一步; 而是使用vetoable listeners, 详情可Google 'vetoable listeners java'. 下面为第二中方法启发性的例子, 而非真正的实现:
// Register for property change events on the bean
bean.addVetoableChangeListener(new MyVetoableChangeListener());
class MyVetoableChangeListener implements VetoableChangeListener {
// This method is called every time the property value is changed
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
// Get the old value of the property
Object oldValue = evt.getOldValue();
// Get the new value of the property
Object newValue = evt.getNewValue();
// Determine if the change should be vetoed, thereby preventing the change
boolean veto = false;
if (veto) {
throw new PropertyVetoException("the reason for the veto", evt);
}
}
}
优点
Cancellable Selection带来的好处是它清晰将选择过程分为两段, 将使用者从必须创建子类解脱出来, 转而允许使用简单的listener. 代码更加清晰.
缺点
Flex的标准Tree/Table等control使用直接选择, 用上面的TreeCancellableSelect需要一定的学习时间 - 需要将习惯的selectItem转而使用currentItem.
回應
For quite a while Micsoft's
For quite a while Micsoft's stance was that there was not to be such an XP mode while the press and the reviewers were felt it was a bad omission it. I am not skeptical about the U Turn by Micsoft but worried about the quality of the XP mode testking 350-001 in W7. Surely if Micsoft produces such a mode at such a late hour - virtually weeks before Aug release - what kind of research and quality will it have built in it?
This reminds me of the days of IE6 & IE7 and IE8 which were being rushed out in haste vcp 410 testking with appalling quality into the market because other browsers were leagues ahead and Micsoft had some serious catching up to do!. The IEs were followed virtually every day by hot fixes and security alerts no end.
XP was a land mark achievement and a testking 642-812 worthy successor. I would like Micsoft to take the same stance and make a serious attempt to build quality in this compatibility so that I can enjoy reliability in W7 when I finally marry my XP into the W7.