Wednesday, October 19, 2011

DataGrid Support for Copy and Paste


This article describes how to add support for copy and paste to the Flex DataGrid component.  It enables one to copy and paste DataGrid selections to/from most desktop applications, notably spreadsheet apps like Excel.

The implementation enables copying the selected rows or cells to the clipboard in a conventional text format, and pasting similarly formatted clipboard text into a DataGrid.  The paste operation applies to the region of cells anchored by the origin of the current selection.

Providing generic support for copy and paste isn't trivial because the DataGrid's data "model" is just a list of Object valued data items.  DataGrid columns typically corespond to data item properties - named by the column's dataField - however the type of the item property displayed in each column isn't specified explicitly.  Even if the type was specified explictly, there is a vast number of data interchange formats one could choose to support and providing a transcoder for any one of them could be a great deal of work.  To keep this example manageable and to ensure the broadest support for its clipboard format, we'll just transcode the DataGrid selection as tab separated values.

To prepare to copy the selected cells to the clipboard we'll convert the dataField property for each selected column in each selected row to a string, using the Object toString() method.

var column:GridColumn = columns.getItemAt(cp.columnIndex) as GridColumn;
var value:Object = dataProvider.getItemAt(rowIndex)[column.dataField];
text += exportText(value, column);  // returns value.toString()

A "fieldSeparator" tab character is inserted in the text in between column values, and a "rowSeparator" newline character is appended to the text at the end of each row.   The resulting text is added to the clipboard as text, using the Flash clipboard API:

Clipboard.generalClipboard.clear();
Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text);

The clipboard is cleared first so that applications which support multiple formats will not be misled by data copied to the clipboard from other sources.

Handling the paste operation is a little bit more complicated because we need to convert the incoming strings to data item property values of the correct type.   We're not trying to handle structured data, so it's only necessary to detect each target property's primitive type.   The name of the type of a data item property named dataField can be discovered (not terribly efficiently) with the describeType() function and some E4X expressions that check for the definition of a public variable or a read/write accessor:

private function columnTypeName(item:Object, column:GridColumn):String
{
    const dataField:String = column.dataField;
    const itd:XML = describeType(item);  // "item type description"
    const variableType:XMLList = itd.variable.(@name == dataField).@type;

    if (variableType.length() == 1)
        return variableType.toString();

    const accessorType:XMLList = 
        itd.(@name == dataField && @access=="readwrite").@type;

    if (accessorType.length() == 1)
        return accessorType.toString();
    
    return null;
}

The code that handles actually pasting the clipboard text into the DataGrid only recognizes the primitive ActionScript types: the numbers, Boolean, String.

If this code were to become a feature of the Flex DataGrid, we'd want to provide a way for developers to change how column values are converted to and from strings.   The example's exportText() and importText() methods are an example of how this might be done.  The exportText method allows one to change how a column's value for a dataProvider is converted to a String, and the importText method enables one to change how an incoming string value for a column is stored in the corresponding data item:

exportText(item:Object, column:GridColumn):String
importText(item:Object, column:GridColumn, text:String):void

By redefining these methods, one could add support for non-primitive datatypes so long as all sources of such clipboard data cooperated.  It would also be possible to convert a different data item property than the one specified by column.dataField.   For example a column whose datafield specified an image valued property might redefine import,exportText() to manage a URL for the resource, rather than the image itself.

There's one other implementation detail that's worth mentioning: listening for the copy and paste keyboard events. You might think that adding a keyDown listener and checking for control-C and control-V keyboard events would be the thing to do (I did).  You would be mistaken (I was).   In fact you might be confused by the fact that although most keyboard events are delivered to the keyDown listener, control-C and control-V are not (I have  may have indulged in some swearing).   It turns out that the events to listen for are Event.COPY and Event.PASTE and the Flash player deals with detecting platform-specific key sequences like command-C, and command-V and mapping them to "copy" and "paste" events (much to my relief).

Here is the source code for the example: DataGridCopyPasteExample.mxml, and here's the example itself.  Try copying and pasting, from this DataGrid to other desktop applications, and from desktop applications that  add tab-separated grid data to the clipboard, to this DataGrid.

More About Approximating Circular Arcs With a Cubic Bezier Path


Back in April of this year I wrote an article called Approximating a Circular Arc With a Cubic Bezier Path which demonstrated how one could do the job with the latest version of Adobe Flex and Actionscript.  The article was based on a paper written by Aleksas Riskus called Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa which explains how a single cubic Bezier curve can be used to approximate an arc of 90 degrees or less.  The paper actually presents two formulas for doing this, in sections (7) and (9), and I'd started with the latter because it seemed like the ActionScript version would be easier to write.  The fact that the formulas didn't work as written was probably for the best, because it forced me to read the paper carefully enough to roughly understand the derivations, at least for section (7).   Which is what I ended up implementing.  I also sent a note to Aleksas Riškus, who is a Mathematics professor in Lithuania, and not long after I'd published my article, he replied with corrections for the equations in section (9):

x2 = xc + ax – k2*ay,
y2 = yc + ay + k2*ax,
x3 = xc + bx + k2*by,                                  (9)
y3 = yc + by – k2*bx


Back then I was grateful for the reply and planned to write a follow-up blog that included a revised version of the ActionScript code.

Then I forgot about it.

Just recently Martin Fox sent me an email about the blog entry.  He pointed out that the circular arcs my demo application rendered were a bit flat when the swept angle was small, and that this was likely due to the constant tangent magnitude ( k*R in section (7) of the Riskus paper).  Simply recoding the method that computed the arcs in terms of the equations in section (9) corrected the problem.   And having reacquainted myself with this material I decided to factor the code for computing arcs into a utility class.   The utility class. ArcPathUtils.as, includes methods for creating spark.primitives.Path objects that render some useful variations on the basic arc:

createArcPathData(
    xc:Number, yc:Number, r:Number, a1:Number, a2:Number):String
createTriangularWedgePathData(
    xc:Number, yc:Number, r:Number, a1:Number, a2:Number):String

createRectangularSegmentPathData(
    xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,
    caps:String="||"):String
createRectangularSegmentPathData(
    xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,
    caps:String="()"):String

createRectangularSegmentPathData(
    xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,
    caps:String="))"):String

createRectangularSegmentPathData(
    xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,
    caps:String=")("):String



This set of static PathArcUtils methods compute the String value for a s:Path's data property.  They all create an arc centered at xc,yc that sweeps from angle a1, to a2.  The center of the arc is specified in pixel coordinates, and the sweep angles in degrees.  The r parameter for createArcPathData() and createTriangularWedgePathData() is the arc's radius, also in pixels.   The createRectangularSegmentPathData() method creates an rectangular arc "segment" whose ends are specified by the final caps parameter.    The arc segment has an inner and outer radius specified by the r1 and r2 parameters respectively.

The PathArcUtils class also includes a createArc() method, similar to createArcPathData(), which just returns the coordinates for the bezier curves as objects.

The application below demonstrates the s:Path arc variations.  The source code for the application can be found here:


You can try the different arc types, and "rectangular arc segment" end cap types, by clicking the buttons in the top row.   There are some limits to the relative values for the start and end angle, and for the inner and outer radius, to prevent the Path from self-intersecting enough to distort the expected shape (see the source code for the details).

Wednesday, July 20, 2011

The Truth About ArrayList, DataGrid, and Christmas Trees


The "High Performance 'Christmas Tree' DataGrids" entry describes how to efficiently update individual cells in a DataGrid so that it twinkles like a Christmas tree when dataProvider items are updated randomly and at a high rate.  The key to getting this done efficiently is using the DataGrid invalidateCell() method to inform the grid about the cells whose contents have changed.

If you create a DataGrid with an ArrayCollection or an ArrayList as its dataProvider, and the grid's columns just display properties of each dataProvider item, and those properties dispatch PropertyChangeEvents when they're set (as bindable properties do by default), then you'll find that it's not necessary to call invalidateCell(), and that the DataGrid is updated automatically.  You'll also find that performance isn't particularly good when the number of columns is large or the item update rate is high. This special case works "automatically" because ArrayList, or the internal ArrayList that's automatically created by ArrayCollection, adds PropertyChange listeners to all of its items and dispatches an "update" CollectionEvent in response to each one.

DataGrid responds to "update" CollectionEvents by redisplaying all of the visible cells whose renderer data is the same as the item that was updated.  This means that one or more rows of renderers will be redisplayed.

The efficiency of applications whose data model matches the special case outlined above can be improved by making a small extension to ArrayList.  The protected itemUpdated() ArrayList method is responsible for dispatching "update" CollectionEvents in response to item PropertyChangeEvents.  What we want it to do instead is to call invalidateCell() for the column (or columns) whose renderers depend on the changed item property and then only if the corresponding item is curently visible.  This can't be done within the ArrayList implementation, since the computation depends on the DataGrid and can be application-specific.  A simple way to deal with that is to have the ArrayList itemUpdated() method delegate to a Function:

public class DataList extends ArrayList
{
    private var itemUpdate:Function = null;
    
    public function DataList(source:Array=null, itemUpdate:Function=null)
    {
        super(source);
        this.itemUpdate = itemUpdate;
    }
    
    override protected function
        itemUpdateHandler(event:PropertyChangeEvent):void
    {
        if (itemUpdate != null)
            itemUpdate(event);
    }    
}

The Application provides the itemUpdate function which is responsible for calling invalidateCell() based on the item and property that changed.  The source code for DataList is here, and the source code for this example application is here.

The example below is similar to the one described in the previous entry on this topic, except that each column just depends directly on one item "colorN" property.


In this example, each dataProvider item is a simple object with one colorN property per column and each column's dataField specifies the colorN the column (GridColumn) depends on.  This makes it easy to map from a PropertyChangeEvent to the row,column location of the cell that needs to be invalidated.   If the PropertyChangeEvent maps to a visible cell, then invalidateCell() is called.

private function handleItemUpdate(event:PropertyChangeEvent):void
{
    const item:Object = event.target;
    const property:String = event.property as String;


    // Find the dataProvider index of item, if currently visible
    var itemIndex:int = -1;
    for each (var rowIndex:int in dataGrid.grid.getVisibleRowIndices())
    {
        if (dataGrid.dataProvider.getItemAt(rowIndex) == item)
        {
            itemIndex = rowIndex;
            break;
        }
    }
    if (itemIndex == -1)
        return;  // item wasn't visible


    // Find column index for event.property, if currently visible
    var propertyIndex:int = -1;
    for each (var colIndex:int in dataGrid.grid.getVisibleColumnIndices())
    {
        if (dataGrid.columns.getItemAt(colIndex).dataField == property)
        {
            propertyIndex = colIndex;
            break;
        }
    }
    if (propertyIndex == -1)
        return;  // column displaying event.property not visible
    
    dataGrid.invalidateCell(itemIndex, propertyIndex);
}


Tuesday, July 19, 2011

High Performance "Christmas Tree" DataGrids


The DataGrid component displays its dataProvider IList of "items" as grid rows.  Each GridColumn can be mapped to an arbitrary aspect of an item: it may display one item property, or several item properties, or the value of some computation based on one or more item properties, etc.

The DataGrid detects changes in its dataProvider by listening for CollectionEvents.  The CollectionEvents notify listeners about list structural changes, like items being added or removed, and they can also report that an item's value has been changed.  This is called a collection "update" event. What collection update events can not report is which grid columns were affected by an item's change.  In other words, CollectionEvents don't provide a way to notify the DataGrid that the contents of an individual grid cell have changed.

Applications can notify the DataGrid of changes to individual cells by calling the DataGrid invalidateCell() method.

invalidateCell(rowIndex:int, columnIndex:int)

The DataGrid's layout handles cell invalidations efficiently by only redisplaying the item renderer at the specified location, and only if it's actually visible. Multiple calls to invalidateCell() are batched up and deferred until the next layout phase, just like calls to invalidateSize() or invalidateDisplayList().

This example demonstrates the use of the DataGrid invalidateCell() method. Pressing the "Run" button starts a timer that updates DataGrid cells at the rate specified with the slider.
Here's the source code for the example.

This "Christmas Tree" DataGrid example displays a dataProvider with items whose substructure is constantly changing.  Each dataProvider item has one object valued property per column, and each of those properties has an object value that defines what's displayed in a single cell.  To keep things simple the column properties are just called "C0", "C1", ... and the value of each property is an object itself, with "color" and "index" properties, like {color:NN, index:NN}. So the data for the cell at rowIndex=10, columnIndex=5:

dataGrid.dataProvider.getItemAt(10)["C5"] => {color:0xFF6C0D, index:4}

The DataGrid has no way of knowing when the color,index data for a cell has been updated so the developer must call invalidateCell(rowIndex, columnIndex) to let the DataGrid know that the specified cell must be redisplayed.  When the "Run" button is toggled on, updateTimerHandler() method  below is called up to 64 times/second and each time it updates a single random cell's color and index properties and then calls invalidateCell().  Calling invalidateCell() only causes the (one) corresponding item renderer to redisplay, so long as doing so doesn't invalidate the corresponding row's height.  That can occur if variableRowHeight=true and if it does, the entire DataGrid is redisplayed.

The CPU load produced by applications like this one is only proportional to the update rate and the renderers' complexity, so long as the overall grid layout isn't affected by the cell updates.  Using fixed height rows with variableRowHeight="false" (the default), simple item renderers, and not dynamically changing column widths, are good ways to ensure that the CPU load is minimized.

Thursday, June 23, 2011

Creating a Custom DataGrid GridItemRenderer with Data Binding


This article describes how to create a custom DataGrid item renderer with GridItemRenderer and data binding.

The GridItemRenderer class is a Group that implements IGridItemRenderer and can be used as the top level "container" for a custom item renderer.  Its data property is the dataProvider item for the row the item renderer appears on and its components can configure themselves by binding to the data property, or by overriding the renderer's prepare() method and configuring themselves imperatively with ActionScript code.  All of the item renderers in this example use binding, which makes the code a little easier to read and modify. It's also less efficient than using a prepare() method override.   The performance difference is most noticeable when the DataGrid is configured so that a large number of item renderers are visible.

DataGrid item renderers are always sized to exactly fit the cell that they're rendering: their size matches the width of their column and the height of their row. The DataGrid enables interactive column resizing by default, so all of the item renderers need to defend against the possibility that their width will become arbitrarily small.  The first column's item renderer just contains a Label.  By specifying maxDisplayedLines="1" we ensure that the Label will truncate its text to fit the width it's given. The other columns' item renderers specify clipAndEnableScrolling="true" which just means that their fixed size contents will always be clipped to the bounds of the GridItemRenderer. This property -enables- scrolling, which means that one could actually scroll the contents of the item renderers when their width gets small, by setting the renderer's horizontalScrollPosition property.  We do not do as much here.


The application's control bar "shake" slider can be used to change the the dataProvider items.
The "Name" column's item renderer demonstrates a special case: if the renderer contains a text component with id="labelDisplay", GridItemRenderer automatically sets the labelDisplay's text property to the value of data[column.dataField] per the GridItemRenderer label property.  The  first column also demonstrates using the styleName property to configure the Label's left, right, top, and fontSize styles.

<s:GridColumn dataField="name" headerText="Name">
    <s:itemRenderer>
        <fx:Component>
            <s:GridItemRenderer>
                <s:Label id="labelDisplay" maxDisplayedLines="1" styleName="nameColumn"/>
            </s:GridItemRenderer>
        </fx:Component>
    </s:itemRenderer>
</s:GridColumn>

We've used styleName here to refer to a set of styles that could have also have been set inline.   The style is defined like this:

<fx:Style>
    .nameColumn {
        fontSize: 18;
        left: 5;
        right: 5;
        top: 9;
    }      
</fx:Style>

The "Statistics" column's item renderer demonstrates how binding can be used to  configure the geometry of graphic elememnts.   The data items' min, max, and value properties have been normalized to the range [0 100] to keep the bindings simple.  The bar chart s:Rect elements specify scaleY="-1" so that the rectangles grow from the bottom of the renderer upwards.

<s:GridColumn dataField="value" headerText="Statistics">
    <s:itemRenderer>
        <fx:Component>
            <s:GridItemRenderer clipAndEnableScrolling="true">
                <s:Group left="5" top="5" bottom="5">
                    <s:Ellipse x="0" y="0" width="30" height="30">
                        <s:stroke>
                            <s:SolidColorStroke color="0x272F32" weight="2"/>
                        </s:stroke>
                        <s:fill>
                           <s:SolidColor color="0x9DBDC6"/>
                        </s:fill>
                    </s:Ellipse>
                    <s:Line rotation="{(data.value / 100) * 360}" transformX="15" transformY="15"
                            xFrom="15" yFrom="15" xTo="27" yTo="15">
                        <s:stroke>
                            <s:SolidColorStroke color="0xFF3D2E" weight="3"/>
                        </s:stroke>
                    </s:Line>
                    <s:Rect x="40" y="30" scaleY="-1" width="15" height="{(data.min / 100) * 30}">
                        <s:fill>
                             <s:SolidColor color="0xFF3D2E"/>
                        </s:fill>
                    </s:Rect>
                    <s:Rect x="60" y="30" scaleY="-1" width="15" height="{(data.max / 100) * 30}">
                        <s:fill>
                            <s:SolidColor color="0xFF3D2E"/>
                        </s:fill>
                    </s:Rect>
                </s:Group>
            </s:GridItemRenderer>
        </fx:Component>
    </s:itemRenderer>
</s:GridColumn>

The "Value" column's item renderer allows one to change data item's value property with a Slider.  The binding is specified with "@{data.value}", which indicates that it's bidirectional, so changing the slider also change the dataProvider item.

<s:GridColumn dataField="value" headerText="Value">
    <s:itemRenderer>
        <fx:Component>
            <s:GridItemRenderer clipAndEnableScrolling="true">
                <s:HSlider left="5" right="5" verticalCenter="0"
                        minimum="{data.min}" maximum="{data.max}" value="@{data.value}"/>
            </s:GridItemRenderer>
        </fx:Component>
    </s:itemRenderer>
</s:GridColumn>

The "Call" column's item renderer demonstrates a slightly more complicated component layout and it also demonstates how the data item's value can be both displayed and edited.

<s:GridColumn dataField="call" headerText="Call">
    <s:itemRenderer>
        <fx:Component>
            <s:GridItemRenderer clipAndEnableScrolling="true">
                <s:HGroup left="5" top="9" right="5" verticalAlign="baseline">
                    <s:CheckBox selected="@{data.call}"/>
                    <s:RadioButton selected="{data.call}" enabled="false"/>
                    <s:TextInput text="{data.call}" enabled="false"/>
                </s:HGroup>
            </s:GridItemRenderer>
        </fx:Component>
    </s:itemRenderer>
</s:GridColumn>

Wednesday, May 18, 2011

Controlling Text Wrapping in DataGrid Item Renderers

This article demonstrates how to control text wrapping with the DefaultGridItemRenderer or a simple custom item renderer based on GridItemRenderer.

Flex text components unconditionally display explicit or "hard" newlines that appear in their text string.  The text components can be configured to automatically insert "soft" newlines to prevent the text from  overlapping the component's left or right edges.   This layout process is called text "wrapping" or  "word wrapping" when the layout endeavors to only insert soft newlines in between words.   Note that soft newlines are displayed but are not inserted into the text string.

The TextField boolean wordWrap property and the Flash Text Engine (FTE) lineBreak style control can be used to enable wrapping.  By default wrapping is disabled.  Specifying wordWrap="true" for TextFields or lineBreak="toFit" for Spark text components like Label, enables automatic word wrapping.  To disable wrapping specify wordWrap="false" or lineBreak="explicit".

The high performance item renderers, DefaultGridItemRenderer and UITextFieldGridItemRenderer, are essentially TextFields, so the wordWrap property is used to control wrapping.  Custom item renderers that include Spark FTE text components can use the lineBreak style.   To simplify a common case, the default item renderer automatically sets wordWrap="true" when variableRowHeight="true" is  specified.

In the following DataGrid example all cells display the same long string that begins with "Lorem Ipsum ...".  The DataGrid's typicalItem, which defines each column's width and the default row height, is  defined with a string that contains a shorter string and a pair of newlines, so that it occupies three lines total:

    <s:typicalItem>
        <s:DataItem value="Lorem Ipsum sample text.&#10;newline&#10;"/>
    </s:typicalItem>

We've used the odd XML '&#10;' escape to introduce two newlines (the value of the newline character is decimal 10).

Here's the live example and the source code.

The first "Implicit wordWrap" column's renderer is DefaultGridItemRenderer (because a renderer was not specified) and it demonstrates the default behavior, which is to enable wrapping when variableRowHeight=true.

    <s:GridColumn dataField="value" headerText="Implicit wordWrap"/>


The second "Explicit wordWrap" column's DefaultGridItemRenderer overrides the wordWrap property.   If the  wordWrap property's value is set explicitly, the renderer ignores the DataGrid's variableRowHeight property.


    <s:GridColumn dataField="value" headerText="Explicit wordWrap">
        <s:itemRenderer>
            <fx:Component>
                <s:DefaultGridItemRenderer
                    color="0x1E6913"
                    wordWrap="{outerDocument.wordWrapCheckBox.selected}"/>
            </fx:Component>
        </s:itemRenderer>
    </s:GridColumn>

The third "Label Renderer" column's renderer is a custom GridItemRenderer that displays its text with an s:Label component.  We use the Label's maxDisplayedLines property to control line breaking and to include "..." truncation when the single line of text doesn't fit.  Note that maxDisplayedLines="0" means occupy as many lines as necessary.

    <s:GridColumn dataField="value" headerText="Label Renderer">
        <s:itemRenderer>
            <fx:Component>
        <s:GridItemRenderer>
                    <s:Label id="labelDisplay"
                        left="5" top="9" right="5" bottom="5"
                        maxDisplayedLines="{outerDocument.maxDisplayedLines}"/>
                </s:GridItemRenderer>
             </fx:Component>
         </s:itemRenderer>
    </s:GridColumn>

In this last column we could have forced the label's text to appear on one line by setting  lineBreak="explicit" as noted earlier.  Doing so is a little bit cheaper, since the text is simply clipped to the right edge of the label in that case.


Tuesday, May 17, 2011

DataGrid Row Heights


This article demonstrates using the DataGrid rowHeight and variableRowHeights property to the height of grid rows.

By default all of the rows in a DataGrid have the same height.  The rows' heights are defined by the rowHeight property and if its value is not specified, the computed "preferred" height of the first row is used.   If the variableRowHeight property is set to true, then the height of each row is the maximum measured height of each cell that's been exposed, so far.  That means that when the DataGrid's columns aren't all visible and variableRowHeight="true",  scrolling new columns into view can cause a row's height to grow.   Similarly, the DataGrid uses the first row's height (or the rendered height of the "typcalItem", if it was specified) as an estimate for the heights of rows that haven't been scrolled into view yet.   This means that the DataGrid Grid's contentHeight, i.e. total height of all of the rows, can grow or even  shrink as it's scrolled, when variableRowHeight=true.  Since the scrollbar's thumb size reflects the content size, you may see its height change a little as you scroll, although as the number of rows gets large, this effect usually isn't noticeable.

Here's a simple example and the source code.  When variableRowHeight="false" (the default) use the slider to change the row height of all the DataGrid's rows. When variableRowHeight="true", each row's height is computed.   With the variableRowHeight checkbox checked, try adding enough text to a string in the  "name" column to cause the renderer to wrap, or resize the column's width by dragging  the header's column separators.
Note that if there's additional vertical space left over after all the rows have been displayed, DataGrid displays row and column separators that indicate empty or "padding" rows at the bottom of the grid.  The padding rows do not contain item renderers and the DataGrid doesn't provide any feedback or support selection for padding rows.   They just exist to show the user the limits of the grid.  The padding rows' height is always the most recent value of rowHeight.

The runtime support for variable height rows requires quite a bit more bookeeping than the  variableRowHeight="false" case and there's a commensurate impact on performance and footprint. That's why by default the DataGrid is configured for fixed height rows.   That said, item renderer complexity tends have a much bigger impact on display/scrolling performance, so developers need not shy away from specifying variableRowHeight=true, even when optimizing for performance.

If you take a look at the source code for this example, you'll see that the slider and label are not bound to the DataGrid's rowHeight property, but to "dataGrid.grid.rowHeight".  The grid is an instance of Grid that's responsible for displaying the cells' item renderers,  and all of the other grid visuals, like the selection and hover indicators.  Many DataGrid properties, like rowHeight, are just "covers" that expose the corresponding Grid property. Generally speaking there's no need to be aware of this however in this case the DataGrid cover property doesn't dispatch binding change events when the corresponding Grid property changes.   This limitation should be corrected in a future release.