不要将Model Object直接赋给List/DataGrid的dataProvider Do NOT Assign Your Model Object to List/DataGrid's dataProvider Directly

AQUA ICONS SYSTEM ALERT CAUTION ICON Rule - a read operation should never result any change in the model

You should not assign your model object directly to List/DataGrid/AdvancedDataGrid's dataProvider property. If you do, your model will be changed when the user performs 'read-only' operations, like sorting the view using a column. If at this time, the model is accessed elsewhere, then all the callers will "see" the same sort order as the user - in many cases this is unacceptable. Even worse, bugs caused by unexpected ordering, filtering are usually hard to catch. It's important that you do not assign your model object directly to List/DataGrid/AdvancedDataGrid's dataProvider property. To generalize this rule, a read operation should never result any change in the model.

Reasoning - you read, and yet model is written!

I'll use a sample application demos the problem described above. This simple application displays employee information on a datagrid. We allow the user to sort the view order in the datagrid. The corresponding code is just below the screenshots.

image  image

public var employees:ArrayCollection = new ArrayCollection([
	{name: "Jack", gender: "M"},
	{name: "Joanne", gender: "F"},
	{name: "Jason", gender: "M"}
]);

private function traceInfo():void {
	trace(ObjectUtil.toString(employees));
}


    
    
    
    

The trace output:

Trace before the 'Name' column header is clicked after
(mx.collections::ArrayCollection)#0 
  filterFunction = (null) 
  length = 3 
  list = (mx.collections::ArrayList)#1 
    length = 3 
    source = (Array)#2 
    ... 
  sort = (null) 
  source = (Array)#2
(mx.collections::ArrayCollection)#0 
  filterFunction = (null) 
  length = 3 
  list = (mx.collections::ArrayList)#1 
    length = 3 
    source = (Array)#2 
    ... 
    sort = (mx.collections::Sort)#6 
    compareFunction = (function) 
    fields = (Array)#7 
      [0] (mx.collections::SortField)#8 
        caseInsensitive = false 
        compareFunction = (function) 
        descending = false 
        name = "name" 
        numeric = (null) 
    unique = false 
  source = (Array)#2

Clearly, the model object employees has been changed. ArrayCollection.getItemAt() may return different objects before datagrid sorting and after. The danger of this can never be emphasized enough.

Solution - use chained ListColletionView to resuce

The solution is very simple. You use a ListCollectionView as a controller in MVC:

When no longer used, you need to set the ListCollectionView's list to null to remove event wire with the model object:

(dg.dataProvider as ListCollectionView).list = null;

ListCollectionView, as the name suggests, is a view on an IList. In this case, the employees object is the IList model for the view. Any sorting, filtering happen at the ListCollectionView only. You can use  ListCollectionView to add, remove items too. The ListCollectionView delegates write operations to the model IList.

The ArrayCollection class extends ListCollectionView, but this does not mean you should assign the ArrayCollection typed model object to dataProvider directly. Model objects, regardless of their types, must be wrapped through ListCollectionView to pass to Lists/DataGrids.

Whenever you need to show sorted, filtered view on the model object and you do not want to apply the sorting, filtering to the model object, you should used chained ListCollectionView. Do remember to remove the event wiring between ListCollectionView and the model object after use.

Note - hello-worlds are fine, but ...

There are thousands of hello-world code snippets that demonstrate various kinds of tips. However, when you browse others' examples, beware that they try to solve one problem and yet their code could create many other problems.

Exceptions - when assigning model object to dataProvider is allowed

One possible exception is that you want the user to edit the order of the elements. In this case, you disable sorting through column headers, set the model object to data grid's dataProvider and implement drag-and-drop to modify the model object directly.

回應

如果我的Model是Array

如果我的Model的属性是Array. 当我要和List/Datagrid进行绑定时,我用ArrayCollection包一下,然后不用了以后,我在用arrayCollection.source把我的Array取回来,是不是你所描述的问题也能相应的解决?

是的.

是的. 你可以直接pass这个array给List的dataProvider; List将自动把Array用ArrayCollection包起来.

我用Array做Model

我用Array做Model的话,像这种List/Datagrid把Model改变的情况,就没了吧?
感觉我自己表述问题不清楚...

是的, 没了.

是的, 没了. Datagrid只改变ArrayCollection而已.