tag:blogger.com,1999:blog-76897073946709425322024-03-19T05:32:35.794-07:00Hans Muller's Flex BlogHans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-7689707394670942532.post-27081085604664886102011-10-19T14:09:00.000-07:002011-10-19T14:09:23.495-07:00DataGrid Support for Copy and Paste<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">var column:GridColumn = columns.getItemAt(cp.columnIndex) as GridColumn;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">var value:Object = dataProvider.getItemAt(rowIndex)[column.dataField];</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">text += exportText(value, column); // returns value.toString()</span><br />
<br />
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:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Clipboard.generalClipboard.clear();</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text);</span><br />
<br />
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.<br />
<br />
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:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">private function columnTypeName(item:Object, column:GridColumn):String</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">{</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const dataField:String = column.dataField;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const itd:XML = describeType(item); // "item type description"</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const variableType:XMLList = itd.variable.(@name == dataField).@type;<br /></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (variableType.length() == 1)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> return variableType.toString();<br /></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const accessorType:XMLList = </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> itd.(@name == dataField && @access=="readwrite").@type;<br /></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (accessorType.length() == 1)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> return accessorType.toString();</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> return null;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<div>
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br /></span></div>
The code that handles actually pasting the clipboard text into the DataGrid only recognizes the primitive ActionScript types: the numbers, Boolean, String.<br />
<br />
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:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">exportText(item:Object, column:GridColumn):String</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">importText(item:Object, column:GridColumn, text:String):void</span><br />
<br />
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.<br />
<br />
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).<br />
<br />
Here is the source code for the example: <a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridCopyPasteExample.mxml">DataGridCopyPasteExample.mxml</a>, 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.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"><a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridCopyPasteExample.mxml">DataGridCopyPasteExample.mxml</a></caption> <tbody>
<tr><td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridCopyPasteExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 321px; margin: 30px 10px 10px 10px; text-align: center; width: 472px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com10tag:blogger.com,1999:blog-7689707394670942532.post-13743167371593532852011-10-19T11:49:00.000-07:002012-01-17T07:49:29.102-08:00More About Approximating Circular Arcs With a Cubic Bezier Path<br />
Back in April of this year I wrote an article called <a href="http://hansmuller-flex.blogspot.com/2011/04/approximating-circular-arc-with-cubic.html">Approximating a Circular Arc With a Cubic Bezier Path </a>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 <a href="http://itc.ktu.lt/itc354/Riskus354.pdf">Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa </a>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):<br />
<br />
<div class="MsoNormal" style="margin-top: 3.0pt; mso-margin-bottom-alt: auto; text-align: justify; text-indent: 14.2pt;">
<i><span lang="ES-TRAD" style="font-size: 10pt;">x2 = xc + </span></i><i><span lang="ES-TRAD" style="color: red; font-size: 10pt;">ax</span></i><i><span lang="ES-TRAD" style="font-size: 10pt;"> – k2*</span></i><i><span lang="ES-TRAD" style="color: red; font-size: 10pt;">ay</span></i><span lang="ES-TRAD" style="font-size: 10pt;">,</span><o:p></o:p></div>
<div class="MsoNormal" style="mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; text-align: justify; text-indent: 14.2pt;">
<i><span lang="ES-TRAD" style="font-size: 10pt;">y2 = yc + </span></i><i><span lang="ES-TRAD" style="color: red; font-size: 10pt;">ay</span></i><i><span lang="ES-TRAD" style="font-size: 10pt;"> + k2*</span></i><i><span lang="ES-TRAD" style="color: red; font-size: 10pt;">ax</span></i><span lang="ES-TRAD" style="font-size: 10pt;">,</span><o:p></o:p></div>
<div class="MsoNormal" style="mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; text-align: justify; text-indent: 14.2pt;">
<i><span style="font-size: 10pt;">x3 = xc +</span></i><i><span style="color: red; font-size: 10pt;"> bx +</span></i><i><span style="font-size: 10pt;"> k2*</span></i><i><span style="color: red; font-size: 10pt;">by</span></i><span style="font-size: 10pt;">, (9)</span><o:p></o:p></div>
<div class="MsoNormal" style="mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; text-align: justify; text-indent: 14.2pt;">
<i><span style="font-size: 10pt;">y3 = yc + </span></i><i><span style="color: red; font-size: 10pt;">by –</span></i><i><span style="font-size: 10pt;"> k2*</span></i><i><span style="color: red; font-size: 10pt;">bx</span></i><o:p></o:p></div>
<br />
<br />
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. <br />
<br />
Then I forgot about it.<br />
<br />
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:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgISZw79vdjOUVGO2cFjzBHO9daJX5kQUdakj2Ek8JwmJyO6gz8jNS2TsDF-NfwqARnn3e89aL6T0cyqEiyopJWqAy04csj89uYM48v5IdgHoZerPTdiIUlS0W0Z_tpr0XpNBZJSZxx-DQ/s1600/arc.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgISZw79vdjOUVGO2cFjzBHO9daJX5kQUdakj2Ek8JwmJyO6gz8jNS2TsDF-NfwqARnn3e89aL6T0cyqEiyopJWqAy04csj89uYM48v5IdgHoZerPTdiIUlS0W0Z_tpr0XpNBZJSZxx-DQ/s1600/arc.png" /></a></div>
<b>createArcPathData</b>(<br />
<span class="Apple-style-span" style="font-family: inherit;"> xc:Number, yc:Number, r:Number, a1:Number, a2:Number):String</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0zMpw7QcH2jppGsNihATyQdDNs8P8fiuzCXNA_TrDvihrSdo8AMw51rcooJPNKMXCfKqCqa3h9iACuO_lf5BUjB1TEgV-Y7PQeweIuHG8Z0nHaRLtMIGv3YOGo8nNV28zCkSffh2ReaA/s1600/wedge.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0zMpw7QcH2jppGsNihATyQdDNs8P8fiuzCXNA_TrDvihrSdo8AMw51rcooJPNKMXCfKqCqa3h9iACuO_lf5BUjB1TEgV-Y7PQeweIuHG8Z0nHaRLtMIGv3YOGo8nNV28zCkSffh2ReaA/s1600/wedge.png" /></a></div>
<b>createTriangularWedgePathData</b>(<br />
xc:Number, yc:Number, r:Number, a1:Number, a2:Number):String<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge9i5AnN7U2Y1L5APIMrf1NAJGYLwT0ApCK6cGiFI_92oAgVQBuI0RqHkIXp4Z1xbktfKa-E7cKsLsGFh_5FM05QEeF7ybmtz-cprfhFYsAib9p7-9PlfITAlo-cxwg9GWDffgnHO1GOI/s1600/segment1.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge9i5AnN7U2Y1L5APIMrf1NAJGYLwT0ApCK6cGiFI_92oAgVQBuI0RqHkIXp4Z1xbktfKa-E7cKsLsGFh_5FM05QEeF7ybmtz-cprfhFYsAib9p7-9PlfITAlo-cxwg9GWDffgnHO1GOI/s1600/segment1.png" /></a></div>
<b><span class="Apple-style-span" style="font-weight: normal;"></span></b><br />
<b>createRectangularSegmentPathData</b>(<br />
xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,<br />
caps:String=<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"||"</span>):String<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu-eqn956bYRbie1ygm1h8FkQoK5KrD2QE9jwkPdJtj6lpoJMzZ7Xmcw48_hTXf2aCGcwizUSDEOtv_ubzZzYr1u06MF7pcbf2LCUJSJUWlYMyP4D4bTZh9o8Wl3zeamKY-dQxNtr7tDM/s1600/segment2.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu-eqn956bYRbie1ygm1h8FkQoK5KrD2QE9jwkPdJtj6lpoJMzZ7Xmcw48_hTXf2aCGcwizUSDEOtv_ubzZzYr1u06MF7pcbf2LCUJSJUWlYMyP4D4bTZh9o8Wl3zeamKY-dQxNtr7tDM/s1600/segment2.png" /></a></div>
<b>createRectangularSegmentPathData</b>(<br />
xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,<br />
caps:String=<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"()"</span>):String<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9aPklYF0RAX-IzMJin7ky6l-BTdE6FZKHDUkv4y5rN4I9gdnO7Vsh-EvAhlxBqwSlRZc4y5GqCeZDgmfA5TflUD0pL947g83VQBEdQgK5cys9exLp6c6QC6-dWmEb7y4YnsYg4khKE28/s1600/setgment3.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9aPklYF0RAX-IzMJin7ky6l-BTdE6FZKHDUkv4y5rN4I9gdnO7Vsh-EvAhlxBqwSlRZc4y5GqCeZDgmfA5TflUD0pL947g83VQBEdQgK5cys9exLp6c6QC6-dWmEb7y4YnsYg4khKE28/s1600/setgment3.png" /></a></div>
<b>createRectangularSegmentPathData</b>(<br />
xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,<br />
caps:String=<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"))"</span>):String<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOOh6Gdbziuc1HEfOK8sZohiZNBtL07s1j_YMHf1hXJmwWo5lUebaTMLSrMrv7nZTqbb__SaVYnuHHFQeOzdj1y8hAhjuZaHlH32XE9BJczysL881t7ZcsI9FgamJdwQpqTBnIoLM_X9g/s1600/setgment4.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOOh6Gdbziuc1HEfOK8sZohiZNBtL07s1j_YMHf1hXJmwWo5lUebaTMLSrMrv7nZTqbb__SaVYnuHHFQeOzdj1y8hAhjuZaHlH32XE9BJczysL881t7ZcsI9FgamJdwQpqTBnIoLM_X9g/s1600/setgment4.png" /></a></div>
<b>createRectangularSegmentPathData</b>(<br />
xc:Number, yc:Number, r1:Number, r2:Number, a1:Number, a2:Number,<br />
caps:String=<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">")("</span>):String<br />
<br />
<br />
<br />
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.<br />
<br />
The PathArcUtils class also includes a createArc() method, similar to createArcPathData(), which just returns the coordinates for the bezier curves as objects.<br />
<br />
The application below demonstrates the s:Path arc variations. The source code for the application can be found here:<br />
<br />
<div>
<div>
<ul>
<li><a href="https://sites.google.com/site/hansmuller/flex-blog/CircularArcSampler.mxml">CircularArcSampler.mxml</a> - The demo application itself.</li>
<li><a href="https://sites.google.com/site/hansmuller/flex-blog/ArcButton.mxml">ArcButton.mxml</a> - A custom component for the demo application.</li>
<li><a href="https://sites.google.com/site/hansmuller/flex-blog/PathArcUtils.as">PathArcUtils.as </a>- The s:Path utility class for creating arcs.</li>
</ul>
</div>
</div>
<br />
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).<br />
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center" border="2" frame="box" rules="NONE"><caption align="bottom"></caption> <tbody>
<tr><td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/CircularArcSampler.swf" style="cursor: hand; cursor: pointer; display: block; height: 583px; margin: 30px 10px 10px 10px; text-align: center; width: 524px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com11tag:blogger.com,1999:blog-7689707394670942532.post-66241150241823297102011-07-20T11:48:00.000-07:002011-07-20T11:53:29.231-07:00The Truth About ArrayList, DataGrid, and Christmas Trees<br />
The<a href="http://hansmuller-flex.blogspot.com/2011/07/high-performance-christmas-tree.html"> "High Performance 'Christmas Tree' DataGrids"</a> 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.<br />
<br />
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. <br />
<br />
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.<br />
<br />
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:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class DataList extends ArrayList</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">{</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private var itemUpdate:Function = null;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> public function DataList(source:Array=null, itemUpdate:Function=null)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> super(source);</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> this.itemUpdate = itemUpdate;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> override protected function</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> itemUpdateHandler(event:PropertyChangeEvent):void</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (itemUpdate != null)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> itemUpdate(event);</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> } </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
The Application provides the itemUpdate function which is responsible for calling invalidateCell() based on the item and property that changed. The<a href="https://sites.google.com/site/hansmuller/flex-blog/DataList.as"> source code for DataList is here</a>, and the <a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridItemUpdateExample.mxml">source code for this example application is here</a>.<br />
<br />
The example below is similar to the one described in the <a href="http://hansmuller-flex.blogspot.com/2011/07/high-performance-christmas-tree.html">previous entry on this topic</a>, except that each column just depends directly on one item "colorN" property.<br />
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"></caption> <tbody>
<tr><td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridItemUpdateExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 380px; margin: 30px 10px 10px 10px; text-align: center; width: 514px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>
<br />
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.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">private function handleItemUpdate(event:PropertyChangeEvent):void</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">{</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const item:Object = event.target;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> const property:String = event.property as String;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br /></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> // Find the dataProvider index of item, if currently visible</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> var itemIndex:int = -1;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> for each (var rowIndex:int in dataGrid.grid.getVisibleRowIndices())</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (dataGrid.dataProvider.getItemAt(rowIndex) == item)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> itemIndex = rowIndex;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> break;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (itemIndex == -1)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> return; // item wasn't visible</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br /></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> // Find column index for event.property, if currently visible</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> var propertyIndex:int = -1;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> for each (var colIndex:int in dataGrid.grid.getVisibleColumnIndices())</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (dataGrid.columns.getItemAt(colIndex).dataField == property)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> propertyIndex = colIndex;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> break;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> if (propertyIndex == -1)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> return; // column displaying event.property not visible</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> dataGrid.invalidateCell(itemIndex, propertyIndex);</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
<br />Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com3tag:blogger.com,1999:blog-7689707394670942532.post-31091507111497011732011-07-19T09:28:00.000-07:002011-07-20T09:30:56.640-07:00High Performance "Christmas Tree" DataGrids<br />
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.<br />
<br />
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.<br />
<br />
Applications can notify the DataGrid of changes to individual cells by calling the DataGrid invalidateCell() method. <br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">invalidateCell(rowIndex:int, columnIndex:int)</span><br />
<br />
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().<br />
<br />
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.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"></caption> <tbody>
<tr><td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridInvalidateCellExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 380px; margin: 30px 10px 10px 10px; text-align: center; width: 514px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>
Here's the <a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridInvalidateCellExample.mxml">source code</a> for the example.<br />
<br />
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 <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">{color:NN, index:NN}</span>. So the data for the cell at rowIndex=10, columnIndex=5:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">dataGrid.dataProvider.getItemAt(10)["C5"] => {color:0xFF6C0D, index:4}</span><br />
<br />
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.<br />
<br />
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.<br />
<br />Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com24tag:blogger.com,1999:blog-7689707394670942532.post-79799542480560327922011-06-23T11:28:00.000-07:002011-06-23T11:29:48.538-07:00Creating a Custom DataGrid GridItemRenderer with Data Binding<br />
This article describes how to create a custom DataGrid item renderer with GridItemRenderer and data binding.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<br />
The application's control bar "shake" slider can be used to change the the dataProvider items.<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
<a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridCustomBindingRendererExample.mxml">DataGridCustomBindingRendererExample.mxml</a>
</caption>
<tbody>
<tr>
<td><embed height="310" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridCustomBindingRendererExample.swf" style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;" type="application/x-shockwave-flash" width="510"></embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
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.<br />
<br />
<s:GridColumn dataField="name" headerText="Name"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:GridItemRenderer><br />
<s:Label id="labelDisplay" maxDisplayedLines="1" styleName="nameColumn"/><br />
</s:GridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
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:<br />
<br />
<fx:Style><br />
.nameColumn {<br />
fontSize: 18;<br />
left: 5;<br />
right: 5;<br />
top: 9;<br />
} <br />
</fx:Style><br />
<br />
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. <br />
<br />
<s:GridColumn dataField="value" headerText="Statistics"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:GridItemRenderer clipAndEnableScrolling="true"><br />
<s:Group left="5" top="5" bottom="5"><br />
<s:Ellipse x="0" y="0" width="30" height="30"><br />
<s:stroke><br />
<s:SolidColorStroke color="0x272F32" weight="2"/><br />
</s:stroke><br />
<s:fill><br />
<s:SolidColor color="0x9DBDC6"/><br />
</s:fill><br />
</s:Ellipse><br />
<s:Line rotation="{(data.value / 100) * 360}" transformX="15" transformY="15"<br />
xFrom="15" yFrom="15" xTo="27" yTo="15"><br />
<s:stroke><br />
<s:SolidColorStroke color="0xFF3D2E" weight="3"/><br />
</s:stroke><br />
</s:Line><br />
<s:Rect x="40" y="30" scaleY="-1" width="15" height="{(data.min / 100) * 30}"><br />
<s:fill><br />
<s:SolidColor color="0xFF3D2E"/><br />
</s:fill><br />
</s:Rect><br />
<s:Rect x="60" y="30" scaleY="-1" width="15" height="{(data.max / 100) * 30}"><br />
<s:fill><br />
<s:SolidColor color="0xFF3D2E"/><br />
</s:fill><br />
</s:Rect><span class="Apple-tab-span" style="white-space: pre;"> </span><br />
</s:Group><br />
</s:GridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><span class="Apple-tab-span" style="white-space: pre;"> </span><br />
</s:GridColumn><br />
<br />
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.<br />
<br />
<s:GridColumn dataField="value" headerText="Value"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:GridItemRenderer clipAndEnableScrolling="true"><br />
<s:HSlider left="5" right="5" verticalCenter="0"<br />
minimum="{data.min}" maximum="{data.max}" value="@{data.value}"/><br />
</s:GridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
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.<br />
<br />
<s:GridColumn dataField="call" headerText="Call"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:GridItemRenderer clipAndEnableScrolling="true"><br />
<s:HGroup left="5" top="9" right="5" verticalAlign="baseline"><br />
<s:CheckBox selected="@{data.call}"/><br />
<s:RadioButton selected="{data.call}" enabled="false"/><br />
<s:TextInput text="{data.call}" enabled="false"/><br />
</s:HGroup><br />
</s:GridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com12tag:blogger.com,1999:blog-7689707394670942532.post-51322908549599061752011-05-18T09:28:00.000-07:002011-10-21T10:12:51.971-07:00Controlling Text Wrapping in DataGrid Item RenderersThis article demonstrates how to control text wrapping with the DefaultGridItemRenderer or a simple custom item renderer based on GridItemRenderer.<br />
<br />
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.<br />
<br />
The TextField boolean <i>wordWrap</i> property and the Flash Text Engine (FTE) <i>lineBreak</i> 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".<br />
<br />
The high performance item renderers, <i>DefaultGridItemRenderer</i> 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.<br />
<br />
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:<br />
<br />
<s:typicalItem><br />
<s:DataItem value="Lorem Ipsum sample text.&#10;newline&#10;"/><br />
</s:typicalItem><br />
<br />
We've used the odd XML '&#10;' escape to introduce two newlines (the value of the newline character is decimal 10).<br />
<br />
Here's the live example and the source code.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
<a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridWrapExample.mxml">DataGridWrapExample.mxml</a>
</caption>
<tbody>
<tr>
<td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridWrapExample.swf" style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;" type="application/x-shockwave-flash" width="500"></embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
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. <br />
<br />
<s:GridColumn dataField="value" headerText="Implicit wordWrap"/><br />
<br />
<br />
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.<br />
<br />
<br />
<s:GridColumn dataField="value" headerText="Explicit wordWrap"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:DefaultGridItemRenderer<br />
color="0x1E6913"<br />
wordWrap="{outerDocument.wordWrapCheckBox.selected}"/><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
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.<br />
<br />
<s:GridColumn dataField="value" headerText="Label Renderer"><br />
<s:itemRenderer><br />
<fx:Component><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> <s:GridItemRenderer><br />
<s:Label id="labelDisplay"<br />
left="5" top="9" right="5" bottom="5"<br />
maxDisplayedLines="{outerDocument.maxDisplayedLines}"/><br />
</s:GridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
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. <br />
<div>
<br /></div>
<br />Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com9tag:blogger.com,1999:blog-7689707394670942532.post-76571512859228575392011-05-17T10:21:00.000-07:002011-05-17T10:22:08.183-07:00DataGrid Row Heights<br />
This article demonstrates using the DataGrid rowHeight and variableRowHeights property to the height of grid rows.<br />
<br />
By default all of the rows in a DataGrid have the same height. The rows' heights are defined by the <i>rowHeight</i> property and if its value is not specified, the computed "preferred" height of the first row is used. If the <i>variableRowHeight</i> 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.<br />
<br />
Here's a simple example and <a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridRowHeightExample.mxml">the source code</a>. 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.<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
<a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridTypicalItemExample.mxml">DataGridRowHeightExample.mxml</a>
</caption>
<tbody>
<tr>
<td>
<embed
width="500"
height="300"
pluginspage="http://www.macromedia.com/go/getflashplayer"
src="https://sites.google.com/site/hansmuller/flex-blog/DataGridRowHeightExample.swf"
style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;"
type="application/x-shockwave-flash">
</embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<div>
<br /></div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com16tag:blogger.com,1999:blog-7689707394670942532.post-57197494520335817382011-05-16T07:48:00.000-07:002011-05-16T07:51:11.466-07:00Using the DataGrid typicalItem to Define Column WidthsThis article demonstrates using the DataGrid typicalItem property to initialize column widths and the invalidateTypicalItem() method to notify the DataGrid about typicalItem property changes.<br />
<br />
The DataGrid's <i>typicalItem</i> is used to compute the initial width of each GridColumn that doesn't specify an explicit width. Although specifying absolute sizes in Flex applications is commonplace, it can be perilous. This is particularly true for components like DataGrid that rely on a separate renderer (factory) to produce the visual element that displays an individual cell. A carefully contrived explicit column width can be invalidated by a host of factors: implicit renderer changes, e.g. due to inherited styles, explicit renderer changes, all of the usual margin/padding and other layout configuration changes, as well as text and text format localizations. An explicit column size's web of dependencies is difficult to manage, particularly in the latter stages of application development. The DataGrid typicalItem property specifies column widths indirectly.<br />
<br />
The typicalItem's value is a representative data item that's used to construct and measure "typical" item renderers when the DataGrid's measured size is computed. If a typicalItem wasn't specified, then the first dataProvider item is used. To compute column widths, a typical renderer is created and validated for each column, its preferred size is recorded, and then the renderer added to the DataGrid's internal free-list. The preferred sizes define the column widths for GridColumn's that don't specify an explicit width, and the DataGrid's rowHeight.<br />
<br />
The advantage of basing column widths on the typicalItem, instead of specifying explicit GridColumnwidths, is that doing so takes all of the factors enumerated earlier into account at runtime, when their final values are known. It eliminates the guess-work inherent in using explicit column widths, or specifying an explicit value for rowHeight.<br />
<br />
Here's an example of a DataGrid that specifies a typicalItem.<br />
<br />
<br />
<s:DataGrid id="dataGrid" requestedRowCount="5" verticalCenter="0" horizontalCenter="0"><br />
<<b>s:typicalItem</b>><br />
<s:DataItem key="99999" name="Typical Item" price="123.45" call="false"/><br />
</<b>s:typicalItem</b>><br />
<br />
<s:ArrayCollection id="items"><br />
<s:DataItem key="1000" name="Abrasive" price="100.11" call="false"/><br />
<s:DataItem key="1001" name="Brush" price="110.01" call="true"/><br />
...<br />
</s:ArrayCollection><br />
</s:DataGrid><br />
<br />
<br />
When the DataGrid's width is unconstrained, as it is in this example, then its overall width is essentially the sum of the column widths. If the DataGrid's width is constrained, for example if left and right are specified, then the typicalItem column widths may be padded so that all of the available horizontal space is allocated.<br />
<br />
Here's a running version of the example, and the source code. Use the input fields at the top of the application to change typicalItem properties.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
DataGridTypicalItemExample.mxml
</caption>
<tbody>
<tr>
<td><embed height="325" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridTypicalItemExample.swf" style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;" type="application/x-shockwave-flash" width="550"></embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
When the application is initialized we insert the typicalItem at the beginning of the DataProvider to help visualize the impact of typicalItem changes. Usually the typicalItem is not displayed. The code that does this is part of the s:Application tag:<br />
<br />
applicationComplete="items.addItemAt(dataGrid.typicalItem, 0)"<br />
<br />
<br />
Because typical item column widths are cached, it's necessary to notify the DataGrid when the typicalItem has changed. This is done by calling the DataGrid<i> invalidateTpyicalItem() </i>method. In this example each input field calls invalidateTypicalItem() when a typicalItem property is changed.<br />
<div>
<br /></div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com0tag:blogger.com,1999:blog-7689707394670942532.post-51828900859640984932011-05-13T12:45:00.000-07:002011-05-16T07:53:52.602-07:00Customizing DefaultGridItemRendererThis article demonstrates how to customize the way DefaultGridItemRenderer displays its data by overriding the prepare() method.<br />
<br />
The most efficient way to render a DataGrid cell is to use <i>DefaultGridItemRenderer</i> (or UITextFieldGridItemRenderer if you're only deploying to Windows) and the most efficient way to configure the displayed elements of any GridItemRenderer is to override its<i> prepare()</i> method. DataGrid calls the prepare() method each time the renderer is redisplayed and the method is intended to be used to configure the renderer based on its properties, notably the data property.<br />
<br />
To configure the text displayed by the DefaultGridItemRenderer we set its<i> label</i> property. The renderer is-a text field and it automatically initializes its text property with the value of the label property. That automatic step happens unconditionally, so setting the DefaultGridItemRenderer text property directly isn't effective. <br />
<br />
To override an item renderer's prepare() method, it's necessary to define a subclass. A complex subclass should be defined in its own file, but simple ones can be defined inline, using the<i> fx:Component</i> tag. The fx:Component tag essentially creates a new top level scope for an anonymous subclass of its root tag. In our case the root tag is always DefaultGridItemRenderer, which is to say that we're defining subclasses of DefaultGridItemRenderer. The prepare() method override has to be specified with ActionScript code which is defined within the fx:Script element.<br />
<br />
Here's an example where each GridColumn's itemRenderer has been defined by a different DefaultGridItemRenderer subclass whose prepare() method initializes the renderer's label and other visual properties.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
<a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridTypicalItemExample.mxml">DataPrepareExample.mxml</a>
</caption>
<tbody>
<tr>
<td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridPrepareExample.swf" style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;" type="application/x-shockwave-flash" width="500"></embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
The DefaultGridItemRenderer subclass definition for the "ID" column is defined as follows. The renderer's data property is set to the dataProvider item displayed by all of the renderer's in the same row. We've specified the GridColumn dataField property, even though it's not used to compute the final value of the renderer's label, so that interactive sorting for this column is based on each item's "key" property.<br />
<br />
<s:GridColumn dataField="key" headerText="ID"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:DefaultGridItemRenderer><br />
<fx:Script><br />
<![CDATA[<br />
override public function prepare(hasBeenRecycled:Boolean):void<br />
{<br />
label = data.name + " (" + data.key + ")";<br />
}<br />
]]><br />
</fx:Script><br />
</s:DefaultGridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
<br />
The prepare() method's hasBeenRecycled parameter, which is not used here, is false if the renderer was just created from scratch, and true if the renderer was used before and had been added to the DataGrid's internal free list.<br />
<br />
The first column in this example displays the item renderer's rowIndex property, which can be useful when the length of the dataProvider is relatively large. In this case we're not displaying any aspect of the renderer's data, just the index of the row that the renderer appears on. It's not useful to sort on this column so we've specified sortable="false".<br />
<br />
<s:GridColumn sortable="false" headerText="Row Index"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:DefaultGridItemRenderer><br />
<fx:Script><br />
<![CDATA[<br />
override public function prepare(hasBeenRecycled:Boolean):void<br />
{<br />
label = String(rowIndex);<br />
}<br />
]]><br />
</fx:Script><br />
</s:DefaultGridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><br />
</s:GridColumn><br />
<br />
The last column in this example sets some DefaultGridItemRenderer styles. DefaultGridItemRenderer is-a textfield and supports a more limited set of text styles than most Spark components. Check the API documentation before assuming that a specific style is supported.<br />
<br />
<s:GridColumn dataField="price" headerText="Price"><br />
<s:itemRenderer><br />
<fx:Component><br />
<s:DefaultGridItemRenderer><br />
<fx:Script><br />
<![CDATA[<br />
override public function prepare(hasBeenRecycled:Boolean):void<br />
{<br />
setStyle("color", (data.call) ? 0x000000 : 0xDD0000);<br />
}<br />
]]><br />
</fx:Script><br />
</s:DefaultGridItemRenderer><br />
</fx:Component><br />
</s:itemRenderer><span class="Apple-tab-span" style="white-space: pre;"> </span><br />
</s:GridColumn><br />
<br />
This approach, i.e. overriding the prepare() method, to customizing a GridColumn's item renderer is efficient but somewhat verbose. Defining the item renderer classes in separate files reduces the GridColumn's MXML markup bulk, but can make the application more cumbersome to read and manage. Item renderers that need to display more than text can be similarly defined with a GridItemRenderer subclass. In that case using data binding instead of a prepare() method override can make the markup a little easier to read however doing so is less efficient.<br />
<div>
<br /></div>
<br />Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com4tag:blogger.com,1999:blog-7689707394670942532.post-27924891892621877302011-05-11T09:35:00.000-07:002011-05-16T07:54:56.049-07:00A Simple Master/Detail Application<br />
This article explains how a simple Spark master/detail application can be created with a Form that's linked to a DataGrid's selectedItem property.<br />
<br />
The value of the DataGrid's <i>selectedItem</i> property is the dataProvider item that's currently selected. This example contains a Datagrid and a Form, and the Form displays various aspects of the selectedItem. Elements of the Form bind to the DataGrid's selectedItem, so when the selection changes the form is updated automatically. In a configuration like this, the DataGrid is the "master" view and the Form is the "detail" view.<br />
<br />
Here's the live example and the <a href="https://sites.google.com/site/hansmuller/flex-blog/MasterDetailExample.mxml">source code</a>. Changing the selected item in the master view, causes the elements of the detail view to be updated. The detail view also allows one dataProvider item property to be changed: if the "call" property is true, it can be set to false by pressing the "Call Now" button.
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center">
<caption align="bottom">
<a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridTypicalItemExample.mxml">MasterDetailExample.mxml</a>
</caption>
<tbody>
<tr>
<td><embed height="350" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/MasterDetailExample.swf" style="cursor: hand; cursor: pointer; display: block; margin: 30px 10px 10px 10px; text-align: center;" type="application/x-shockwave-flash" width="500"></embed>
</td>
</tr>
</tbody>
</table>
</div>
<br />
The Form's selectedItem bindings are not valid unless there's a non-null selection, so we've specified requireSelection="true" for the DataGrid to ensure that there's always a valid selection. Some of the bindings in the detail view are just simple references to properties of the selected item:<br />
<br />
<s:Label text="{dataGrid.selectedItem.name}"/><br />
<br />
<br />
The bindings that connect to prices use a Spark CurrencyFormatter object (<i>cf</i> in the code below) to covert Numbers to nicely formatted localized strings:<br />
<br />
<s:Label text="Tax: {cf.format(dataGrid.selectedItem.price * 0.0975)}"/><br />
<br />
And finally this binding converts a boolean value to either "Yes" or "No":<br />
<br />
<s:Label text="{(dataGrid.selectedItem.call) ? 'Yes' : 'No'}"/><br />
<div>
<br /></div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com1tag:blogger.com,1999:blog-7689707394670942532.post-69895937851277078442011-05-10T11:52:00.000-07:002011-05-16T07:55:58.323-07:00A Simple GridColumn DataGrid Example<br />
A DataGrid's <i>columns</i> are specified by an ordered list of <i>GridColumn</i> objects. GridColumns define what aspect of each dataProvider item is displayed, typically by specifying a <i>dataField</i>, the name of an item property. If a list of columns is not provided, then the DataGrid creates one GridColumn for each public property in the first dataProvider item. This is usually inadequate for applications, since developers will want to control the order columns are displayed in, along with how the columns and data items are displayed. For example developers can specify GridColumn properties like headerText, the text that appears in the column's header.<br />
<br />
Here's a simple DataGrid example with explicitly specified columns.<br />
<br />
<s:DataGrid id="dataGrid" left="10" right="10"><br />
<s:columns><br />
<s:ArrayList><br />
<s:GridColumn dataField="key" headerText="Key"/><br />
<s:GridColumn dataField="name" headerText="Name"/><br />
<s:GridColumn dataField="price" headerText="Price"/><br />
<s:GridColumn dataField="call" headerText="Call"/><br />
</s:ArrayList><br />
</s:columns><br />
<br />
<s:ArrayCollection><br />
<s:DataItem key="1000" name="Abrasive" price="100.11" call="false"/><br />
<s:DataItem key="1001" name="Brush" price="110.01" call="true"/><br />
...<br />
</s:ArrayCollection><br />
</s:DataGrid><br />
<div>
<br /></div>
<br />
A DataGrid's columns property is an IList, just like the dataProvider, and can defined with an ArrayList. So, per the example, dataGrid.columns.length returns the total number of columns and dataGrid.columns.getItemAt(index) returns the GridColumn at the specified index. The columns list is mutable, which means that GridColumns can be added or removed at any time. Note that in this case an ArrayList is preferable to an ArrayCollection, which is often used for the dataProvider, because the columns will not be filtered or sorted.<br />
<br />
Column widths do not depend on the column's headerText, they're based on the rendered widths of the DataGrid's typicalItem. If that's not specified, then the first data item is used. That's the case here. Each column can specify an explicit width and by default all GridColumns are resizable="true", which means that the column can be interactively resized by dragging the edges between columns. Interactively resizing column widths changes the DataGrid's measuredWidth which can be a little disconcerting if the DataGrid's width is not constrained. In this example we've constrained the DataGrid's left and right edges which prevents changes in the DataGrid's measuredWidth from affecting its actual width.<br />
<br />
<br />
Here's a running version of the example, and the <a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridColumnsExample.mxml">source code</a>. Try resizing and sorting columns by dragging the edges between column headings or clicking on the column headings respectively.<br />
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"></caption> <tbody>
<tr><td><embed height="300" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridColumnsExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 300px; margin: 30px 10px 10px 10px; text-align: center; width: 375px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com8tag:blogger.com,1999:blog-7689707394670942532.post-79509900040600303582011-05-09T07:52:00.000-07:002011-05-16T07:56:36.702-07:00DataItem: Bindable Objects for DataGrid ExamplesMany Flex SDK examples include untyped sample data defined with fx:Object like this:<br />
<br />
<fx:Object index:123 text:"Hello World"/><br />
<br />
The fx:Object MXML tag is similar to using ActionScript's Object literal syntax:<br />
<br />
{index:123, text:"Hello World"}<br />
<br />
In both cases the result is a dynamic untyped object instance with three properties. If you use objects defined this way as List or DataGrid dataProvider items and your item renderers bind to the dataProvider properties then the SDK runtime issues warnings like this:<br />
<br />
warning: unable to bind to property 'index' on class 'Object' (class is not an IEventDispatcher)<br />
<br />
A warning of this kind will be issued for each object and each property your renderer attempts to bind to. What the warning is trying to tell you is that it has used the binding to get the initial value of the 'index' property but it will not detect subsequent changes to the property. In other words it's not functioning as a "binding so much as an initialization.<br />
<br />
To make a property bindable you need to include a [Bindable] metadata statement in the property's definition, and to do that you'd need to define a new concrete ActionScript class like:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> public class MyDataItem</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> [Bindable] public var index:int;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> [Bindable] public var text:String;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> }</span> <br />
<br />
Given that, you could define instances of MyDataItem in your MXML application and safely bind to them Here's a demo and the <a href="https://sites.google.com/site/hansmuller/flex-blog/MyDataItemDemo.mxml">source code</a>.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom">MyDataItem: Concrete class with [Bindable] properties</caption> <tbody>
<tr><td><embed height="177" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/MyDataItemDemo.swf" style="cursor: hand; cursor: pointer; display: block; height: 100px; margin: 30px 10px 10px 10px; text-align: center; width: 350px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>
<br />
Defining a concrete class with bindable properties is most efficient and flexible way to define application data. However in small examples or prototype applications we often prefer to use fx:Object because it's a little simpler and eliminates the need for a separate class definition. The <b>Spark DataItem class</b> is a replacement for fx:Object that defines bindable properties. It's called DataItem because it's typically used to create data provider items for Lists or DataGrids.<br />
<br />
In the following Spark List example DataItem is used to define the list's dataProvider items and typicalItem. The application includes a slider with id="slider" and the list's dataProvider, is defined like this:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> <s:dataProvider></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> <s:ArrayCollection></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> <s:DataItem</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> min="{slider.value - 25}"</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> max="{slider.value + 25}"</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> value="{slider.value}"/></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> ...</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </s:ArrayCollection></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </s:dataProvider></span><br />
<br />
So each DataItem has bindable properties named min, max, value, and those properties are bound to the slider's value. The item renderer is defined like this:<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> <s:ItemRenderer fontSize="24"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> <s:Label text="{data.min} {data.value} {data.max}"/></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </s:ItemRenderer></span><br />
<br />
Remember that an item renderer's data property is alway set to the dataProvider item the renderer is to display, so expressions like "{data.min}" are bindings to a DataItem property.<br />
<br />
Here's the application itself, and the <a href="https://sites.google.com/site/hansmuller/flex-blog/DataItemExample.mxml">source code</a>.<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"></caption> <tbody>
<tr><td><embed height="177" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataItemExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 275px; margin: 30px 10px 10px 10px; text-align: center; width: 375px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com0tag:blogger.com,1999:blog-7689707394670942532.post-21278299886939165412011-05-06T08:04:00.000-07:002011-05-16T07:52:05.140-07:00A Minimal DataGrid Example<br />
This is the first in a series of short items about the new Spark DataGrid component. The DataGrid displays a list of data items in a grid of rows and columns, one item per row and one aspect of each item (usually a property) in each column. The Spark DataGrid's capabilities and API are similar to the previous MX version of the Flex SDK. The Spark version is skinnable, supports "smooth scrolling", and all of its visual elements, including item renderers, can be Spark components or graphic elements.<br />
<br />
A detailed specification for the Spark DataGrid can be found on <a href="http://opensource.adobe.com/wiki/display/flexsdk/Spark+DataGrid">opensource.adobe.com</a>. The API documentation for DataGrid can be found on <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/spark/components/DataGrid.html">help.adobe.com</a>.<br />
<br />
This example depends on the <a href="http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK">4.5 release of the Flex SDK</a> which made its debut on May 2nd, 2011. The easiest way to give it a try is by giving the new <a href="https://www.adobe.com/cfusion/tdrc/index.cfm?product=flash_builder">4.5 version of Flash Builder</a>.<br />
<br />
In this very simple DataGrid example an ArrayCollection of DataItems is displayed.<br />
<br />
<s:DataGrid requestedRowCount="5"><br />
<s:ArrayCollection><br />
<s:DataItem key="1000" name="Abrasive" price="100.11" call="false"/><br />
<s:DataItem key="1001" name="Brush" price="110.01" call="true"/><br />
...<br />
</s:ArrayCollection><br />
</s:DataGrid><br />
<br />
The ArrayCollection defines the DataGrid's <span class="Apple-style-span" style="font-family: inherit;"><i>dataProvider</i></span>. The DataGrid displays one dataProvider item per row, and it automatically creates one column for each property in the first item. The dataProvider property tag isn't specified because it's the default property and leaving it out makes the code look just a little simpler. Although any class that implements IList can be used an a dataProvder, ArrayCollection is convenient because it enables the column sorting support.<br />
<br />
We've used the <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/spark/utils/DataItem.html">Spark DataItem</a> class to create dataProvider items for this example although in this case fx:Object would have worked equally well, because the example doesn't rely on data item property bindings. DataItem is used to define an untyped Object whose properties are bindable.<br />
<br />
The DataGrid <i>requestedRowCount</i> specifies the number of rows to be displayed. Since more dataProvider items are specfied, the DataGrid's Scroller makes its vertical scrollbar visible.<br />
<br />
Here's the example itself, and the source code.
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"><a href="https://sites.google.com/site/hansmuller/flex-blog/DataGridMinimalExample.mxml">DataGridMinimalExample.mxml</a></caption> <tbody>
<tr><td><embed height="177" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/DataGridMinimalExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 175px; margin: 30px 10px 10px 10px; text-align: center; width: 350px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com1tag:blogger.com,1999:blog-7689707394670942532.post-28993877453832445842011-04-11T10:17:00.000-07:002011-10-19T11:53:36.826-07:00Approximating a Circular Arc With a Cubic Bezier Path[Note: I've written an update to the material presented here, it's <a href="http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html">More About Approximating Circular Arcs With a Cubic Bezier Path</a>]<br />
<br />
A few days ago I decided to create a circular arc in a Flex program. Of course I could have just used the Flash drawing API and the Graphics curveTo() method, but what I wanted was a FXG graphic element. Like 'Rect' or 'Ellipse' or 'Line'. <br />
<br />
There is no 'CircularArc' graphic element. This was mildly disappointing, but I assumed that there was some pat algorithm for creating an approximation to a circular arc using the Path primitive. Among the Path primitive's many talents is support for drawing quartic or cubic Bezier curves and I expected to find that somone had already sorted out how to coax one into approximating an arc. I searched in vain for a nicely coded example and eventually gave up. So I set about solving the problem using more basic sources. There's a paper on this topic called "<a href="http://itc.ktu.lt/itc354/Riskus354.pdf">Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa</a>" which explains how a single cubic Bezier curve can be used to approximate an arc of 90 degrees or less.<br />
<br />
The derivation of the the control points for a 90 degree circular arc is pretty simple. The control points are on the tangent lines at the curves's endpoints, so if the curve begins on the X axis, the first tangent line is vertical and the second one is horizontal. The distance from the end points to the control points is just r*k, where r is the arc's radius and k is a constant:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeNX7OX7VH5ctYByKYARZ16tLDACZyag4bw5clpLdYzeR9d2T3UoC6yt3-ZLP1g_W9dU_PzldtLRKBzVubkxYvTUB4p3qybo-_sa0XVOUY4tkzJchdL8jiZRkb3m4gPOwbAHGvsbMaja4/s1600/CircularArc-k-equation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeNX7OX7VH5ctYByKYARZ16tLDACZyag4bw5clpLdYzeR9d2T3UoC6yt3-ZLP1g_W9dU_PzldtLRKBzVubkxYvTUB4p3qybo-_sa0XVOUY4tkzJchdL8jiZRkb3m4gPOwbAHGvsbMaja4/s1600/CircularArc-k-equation.png" /></a></div>
<br />
That's k = 0.5522847498, and it's generally referred to as the "magic number". I am not making this up.<br />
<br />
The derivation of the control points for an arc of less than 90 degrees is a little more complicated. If the arc is centered around the X axis, then the length of the tangent line is r * tan(a/2), instead of just r. The magnitude of the vector from each arc endpoint to its control point is k * r * tan(a/2).<br />
<br />
The arc's x1,y2 and x4,y4 end points, and the control points, x2,y2 and x3,y3 are symmetric relative to the X axis:<br />
<br />
x4 = r * Math.cos(a/2);<br />
y4 = r * Math.sin(a/2);<br />
x1 = x4;<br />
y1 = -y4<br />
<br />
x2 = x1 + k * tan(a/2) * y4;<br />
y2 = y1 + k * tan(a/2) * x4;<br />
x3 = x2;<br />
y3 = -y2;<br />
<br />
There's a deriviation of this in the Riskus paper although the explanation of the magnitude of the control point vector gets short shrift. <br />
<br />
Given this forumula for computing the control points of an arc that spans between 0 and 90 degrees that is centered around the X axis, we can create a list of curves that span a total of up to 360 degrees. Each list element's four points - the curve's two end points and two control points - must be rotated to into their final position on the circle.<br />
<br />
The example below uses an ActionScript implementation of this algorithm to compute up to four cubic Bezier curves which are combined in a single Path graphical element. The Path begins at the start angle, and ends at the end angle. Remember that the Y axis increases downwards, so positive angles rotate clockwise from the X axis. The red points correspond to the endpoints of the curves and the blue points indicate the control points. If you vary the angles you'll see that the algorithm strings together as many 90 degree arcs as possible, and then finishes with one small arc.<br />
<br />
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom"><a href="https://sites.google.com/site/hansmuller/flex-blog/CircularArc.mxml">CircularArc.mxml</a></caption> <tbody>
<tr><td><embed height="177" pluginspage="http://www.macromedia.com/go/getflashplayer" src="https://sites.google.com/site/hansmuller/flex-blog/CircularArc.swf" style="cursor: hand; cursor: pointer; display: block; height: 325px; margin: 30px 10px 10px 10px; text-align: center; width: 325px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>
<br />
<br />
To use <a href="https://sites.google.com/site/hansmuller/flex-blog/CircularArc.mxml">this code</a> in other applications, lift the static createArc() and createSmallArc() methods, as well as the EPISILON constant. The createArc() method returns an array of objects, one per Bezier curve, for an arc centered at the origin:<br />
<br />
createArc(radius:Number, startAngle:Number, endAngle:Number):Array<br />
<br />
Each curve is represented by an object with four x,y points: x1, y1, x2, y2, x3, x4, y4.<br />
Points x1,y1 and x4,y4 are the curve's endpoints, x2,y2 and x3,y3 are the control points. To translate the curves to an arbitrary center point xc, yc, just add xc to the x coordinates, yc to the y coordinates. The displayArc() method in the example does this, and demonstrates how to use the curve objects to create the data for a Path graphic element. Sadly the Path's data is a String, so all of the coordinates must be converted to text. On the upside, if you've been searching for an example that demonstrates how to approximate a circular arc with Beziers in Flex, here you are.<br />
<div>
<br /></div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com13tag:blogger.com,1999:blog-7689707394670942532.post-46888856720429016262009-06-08T15:29:00.000-07:002011-02-18T08:31:37.328-08:00Introduction to Viewports and Scrolling in Gumbo<div>
<div>
Computer displays are never big enough. At the moment I'm using a 14x10" laptop display that's about 120 dpi. I've got a 24inch display at my desk back at work and if had my druthers the display where I spend my quality time would be as big as the office walls. And if the rule about personal income holds true - you always feel as though you'd be satisfied if your income was about 3X what you're making now - then I probably wouldn't be truly happy until I needed my own personal movie theater just to read email. That scenario doesn't seem likely until the day comes when the computer's display is wired directly into my visual cortex. That is a development I'm willing to wait for future generations to beta test.<br />
<br /></div>
<div>
</div>
<div>
</div>
<div>
One of the reasons big displays are so attractive is that we often interact with documents which are much bigger than our puny screens. This has been a computer problem since Teletypes slowly slapped upper case text on a fat scroll of butcher paper. It continued to be a problem when 80 line CRT terminals were considered a luxury, and it's still a problem now, even with displays that are so big you have to move your head to take them all in. The modern solution for displaying a large document on a small display hasn't really changed over the years, in a way it's no different than the approach an Egyptian scribe would have employed thousands of years ago to read a papyrus scroll.<br />
<br /></div>
<div>
</div>
<div>
We give the user a way to "scroll" a small rectangular window that exposes a large document a little bit at a time. When we say scrolling, we're referring to moving the origin of the little clipping window relative to the large document. One conventional GUI for scrolling is a pair of vertical and horizontal scrollbars, whose thumb positions define the X and Y origin of the clipping rectangle.<br />
<br /></div>
<div>
</div>
<div>
Here's a diagram that shows the basic geometry of the situation in terms of a "viewport", a GUI container that exposes a rectangular clip of its contents.
<br />
<br /></div>
<div>
<a href="http://sites.google.com/site/hansmuller/Home/archive/viewport-diagram.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" src="http://sites.google.com/site/hansmuller/Home/archive/viewport-diagram.png" style="cursor: hand; cursor: pointer; display: block; height: 447px; margin: 0px auto 10px; text-align: center; width: 542px;" /></a>
</div>
<div>
The Flex Gumbo SDK provides MXML and ActionScript support for this model of scrolling. A new component called <b>Scroller</b> connects a pair of scrollbars to a viewport and manages the layout of the three parts in a conventional way.<br />
<br /></div>
<div>
</div>
<div>
Scroller binds the horizontal and vertical scrollbars' values (which are based on their thumbs' positions) to the viewport's horizontalScrollPosition and verticalScrollPosition properties. The size of the scrollbar thumbs and the maximum limit for their values is based on the relative size of the viewport's content, which is defined by properties called contentWidth and contentHeight. The origin of the viewport's clipping rectangle is defined by the scrollPosition and its size (width, height) is bound to the viewport's size. The viewport displays the content in the clipping rectangle.<br />
<br /></div>
<div>
</div>
<div>
The code below is a simple example of the Scroller class. The Scroller's viewport is a Group that contains an Image:<br />
<br /></div>
<div>
</div>
<div border="0">
<pre> <?xml version="1.0" encoding="utf-8"?>
<span style="color: #3366ff;"><s:Application</span>
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"<span style="color: #3366ff;">></span>
<span style="color: #3366ff;"><s:Scroller</span> left="1" right="1" top="1" bottom="1">
<span style="color: #3366ff;"><s:Group></span>
<span style="color: #3366ff;"><mx:Image</span> source="guyrobot.jpg"<span style="color: #3366ff;">/></span>
<span style="color: #3366ff;"></s:Group></span>
<span style="color: #3366ff;"></s:Scroller></span>
<span style="color: #3366ff;"></s:Application></span>
</pre>
<pre><span style="color: #3366ff;">
</span></pre>
</div>
<div>
Here's a working version of the example. It's been complicated a little to display the current values of the various viewport properties. The source code for this version of the example can be found <a href="http://sites.google.com/site/hansmuller/Home/archive/BlogScrollerExample.mxml">here</a>.</div>
<div>
</div>
<div style="border-color: #d0d0d0; border-style: solid; border: 1px; height: 100%; width: 100%;">
<table align="center"> <caption align="bottom">The Scroller Example in Action</caption> <tbody>
<tr><td><embed height="177" pluginspage=" http://www.macromedia.com/go/getflashplayer" src="http://sites.google.com/site/hansmuller/Home/archive/BlogScrollerExample.swf" style="cursor: hand; cursor: pointer; display: block; height: 200px; margin: 30px 10px 10px 10px; text-align: center; width: 350px;" type="application/x-shockwave-flash" width="164"></embed></td></tr>
</tbody></table>
</div>
<div>
</div>
<div>
<br />
The basic Flex Gumbo container classes are called Group and DataGroup and they share a common base class called GroupBase. GroupBase is-a IViewport, so anything you can create within a Group or a DataGroup can be displayed in a Scroller, since Scroller just requires its viewport to implement IViewport.</div>
<div>
</div>
<div>
Most of the IViewport interface should look familiar based on the discussion so far:<br />
<br /></div>
<div>
</div>
<div border="0">
<pre> public interface <span style="color: #3366ff;">IViewport</span> extends IVisualElement
{
function get <span style="color: #3366ff;">width</span>():Number;
function get <span style="color: #3366ff;">height</span>():Number;
function get <span style="color: #3366ff;">contentWidth</span>():Number;
function get <span style="color: #3366ff;">contentHeight</span>():Number;
function get <span style="color: #3366ff;">horizontalScrollPosition</span>():Number;
function set <span style="color: #3366ff;">horizontalScrollPosition</span>(value:Number):void;
function get <span style="color: #3366ff;">verticalScrollPosition</span>():Number;
function set <span style="color: #3366ff;">verticalScrollPosition</span>(value:Number):void;
function <span style="color: #3366ff;">getHorizontalScrollPositionDelta</span>(scrollUnit:uint):Number;
function <span style="color: #3366ff;">getVerticalScrollPositionDelta</span>(scrollUnit:uint):Number;
function get <span style="color: #3366ff;">clipAndEnableScrolling</span>():Boolean;
function set <span style="color: #3366ff;">clipAndEnableScrolling</span>(value:Boolean):void;
}
</pre>
<pre>
</pre>
</div>
<div>
</div>
<div>
A viewport's width and height are its actual width and height and its contentWidth,Height is the maximum X and Y extent of its contents. For example if a viewport contained a rectangle with origin at 10,20 and width,height of 100, then it's contentWidth would be 110, contentHeight 120. In the example above the Image's origin is 0,0 (the default) and so the Group's contentWidth,Height is the same as the image's width and height.<br />
<br /></div>
<div>
</div>
<div>
There are two additional features in the IViewport interface: the scrollPositionDelta methods that compute the distance to needed to scroll by a line or a page and the clipAndEnableScrolling flag that triggers use of the Flash DisplayObject scrollRect property to clip and render efficiently. Once the clipAndEnableScrolling flag is set, changing the scrollPosition properties will scroll the viewport. When you click on an Scroller's scrollbar track or up/down buttons, roll the mouse wheel, or press they keyboard page up/down or arrow keys, the viewport's scrollPositionDelta methods are used to compute the distance to be scrolled.<br />
<br /></div>
<div>
</div>
<div>
The Gumbo viewport and scrolling API is similar to the one provided by Halo. As before, the fundamental container classes, Container in Halo, Group and DataGroup in Gumbo, are capable of scrolling. In Gumbo that capability is disabled by default, to enable it one must set the Group or DataGroup's clipAndEnableScrolling flag to true. In Halo all containers would sprout scrollbars whenever they detected the need, but in Gumbo if you want conventional scrolling via scrollbars you need to put your viewport in a Scroller component. These changes were motivated by a desire to reduce the cost of typical applications. Most containers in most application don't need scrollbars or scrolling and there's a cost in complexity and footprint for giving all containers that ability in Halo. Gumbo's policy is sometimes referred to as "pay as you go", which is to say that your application's footprint and performance should just reflect the SDK features that you need.<br />
<br /></div>
<div>
</div>
<div>
I always fight the temptation to conclude a blog by announcing that I'm planning to write more on the same topic. That's because more often than not I fail to actually deliver the goods. This time will have to be an exception because the viewport/scrolling topic still warrants a great deal more explanation and a collection of helpful examples. Eventually all of this material will also be covered in formal specs and ASDoc but I think it's worth publishing the details here and now, while it's still new enough to change.<br />
<br /></div>
<div>
</div>
<div>
Note also: there are more robots like the one in the example at <a href="http://guyrobot.com/">http://guyrobot.com/</a>.</div>
<div>
</div>
</div>Hans Mullerhttp://www.blogger.com/profile/13225910609140131153noreply@blogger.com10