This casestudy demonstrates how to use PrintAdvancedDataGrid
and a custom itemRenderer
to print multiple pages of data and graphics, using the SWFloader
component.
Final Result Preview
Let's take a look at the final result we will be working towards - below is an overview of a 12 page PDF printed from a Flex application using the techniques in this article:
Demonstrated in This Tutorial:
- Using FlexPrintJob class.
- Using
PrintAdvancedDataGrid
. - Creating a custom
itemRenderer
with aSWFLoader
. - Printing a header and footer.
- Filtering a
collectionView
. - Printing a
DataGrid
with height that exceeds 7500.
Some Difficulties With Regards to Flex Printing:
- Your content or
DataGrid
rows must all be visible on the stage or they won't print. - Adding manual page breaks is difficult, and achieved through setting your
rowHeight
to match what you want displayed on each printed page. - Flex printing goes haywire if your total
PrintAdvancedDataGrid
content height is greater than approximately 7500 pixels, necesitating a workaround of multiplePrintAdvancedDataGrids
.
Step 1: Import the Printing Classes
Begin by importing the necessary print classes, as well as your print renderer into your Flex project:
import mx.printing.FlexPrintJob; import mx.printing.FlexPrintJobScaleType; import com.reiman.PrintItemRenderer;
Step 2: Create a View for PrintAdvancedDataGrid
and itemRenderer
Create a view State for your bulk printing. In my case, I created a state called printState.
*alternatively, you can create an instance of your PrintAdvancedDataGrid
and itemRenderer and add them to the stage via actionscript, but for a bulk printing project I found this to be too difficult to work with, and as I was trying to print SWFs that are XML driven, I needed to make sure that my SWFs were displaying correctly before printing.
Step 3: Add Your PrintAdvancedDataGrid
<mx:PrintAdvancedDataGrid paddingTop="0" paddingBottom="0" visible="true" rowHeight="200" sizeToPage="true" showHeaders="false" id="printGrid" creationComplete="stripQuiz();" height="{grid_height}" width="300"> <mx:columns> <mx:AdvancedDataGridColumn width="200" id="printColumn" itemRenderer="com.reiman.PrintItemRenderer" sortable="false" /> </mx:columns> </mx:PrintAdvancedDataGrid>
If you need a total printing area greater than 7500 in height, you'll need another PrintAdvancedDataGrid
for each area
Step 4: Set Your PrintAdvancedDataGrid
options
One thing I found important was to set the option
sizetoPage="true"
According to the PrintDataGrid Control page from the Adobe Flex 3 LiveDocs, the sizeToPage property makes sure that the PrintAdvancedDataGrid control removes any partially visible or empty rows and to resize itself to include only complete rows in the current view.
Step 5: Add Your itemRenderer
Add an itemRenderer to your AdvancedDataGridColumn
.
<mx:AdvancedDataGridColumn width="200" id="printColumn" itemRenderer="com.reiman.PrintItemRenderer" sortable="false" />
Step 6: Create the itemRenderer
Component
In my case, I wanted to accomplish several things with the itemRenderer
:
- add a
SWFLoader
- add a header with the page title
- add a side footer with my url
- add spacing to the top, bottom and sides for proper printing
Since my URL worked better as vertical text, I found it much better to use a SWF for this text instead of a text box. You can be creative with your itemRenderer
, adding data items from your XML or datasource as well as logos, URLs, etc. Here are the display items from inside my code for the itemRenderer
component, which is also found in the src/com/reiman/PrintItemRenderer.mxml file in the download package for this tutorial:
<mx:VBox width="100%" > <mx:Spacer height="10" width="100%"/> <mx:HBox horizontalAlign="center" width="100%"> <mx:Text textAlign="center" styleName="printHeader" text="{appy.printGrid.dataProvider.getItemAt(0).title} - {row.lessonTitle} © Insta Spanish 2011" width="92%" x="10" y="-7"/> <mx:Image right="20" source="{row.lessonIcon=='video'?video: row.lessonIcon=='worksheet'?worksheet: row.lessonIcon=='lesson'?activity: row.lessonIcon=='hover'?hover: row.lessonIcon=='drag'?matching: row.lessonIcon=='song'?song: row.lessonIcon=='memory'?memory: row.lessonIcon=='downloads'?'assets/images/icons/downloads.swf': row.lessonIcon=='hangman'?hangman: row.lessonIcon=='quiz'?quiz: row.lessonIcon=='wordgame'?wordgame: row.lessonIcon=='flashcards'?flashcard: row.lessonIcon=='oral'?oral: row.lessonIcon=='cloze'?cloze:empty}" height="25" width="25"/> </mx:HBox> </mx:VBox> <mx:VBox id="print_vbox" horizontalScrollPolicy="off" verticalScrollPolicy="off" verticalAlign="middle" height="100%" width="100%"> <mx:Spacer height="5%" /> <mx:HBox horizontalScrollPolicy="off" verticalScrollPolicy="off" verticalAlign="middle" height="100%" width="100%"> <mx:Spacer width="3%" /> <mx:SWFLoader id="swfPrintLoader" height="90%" width="90%" scaleContent="true" source="{row.fileName}" autoLoad="true" /> <mx:Spacer height="1%" /> <mx:SWFLoader height="198" width="20" scaleContent="true" source="assets/images/wwwinstaspanishcom.swf"/> </mx:HBox> </mx:VBox>
Step 7: Add the Basic Actionscript FlexPrintJob
Code to Your main.mxml File
public function print():void { var printJob:FlexPrintJob = new FlexPrintJob(); printJob.printAsBitmap=false; if(printJob.start()) { printJob.addObject(printGrid); printJob.send(); } }
Step 8: Set printJob
Scale Type
Choose the printJob
scale type you want to use (to read about FlexPrintJob
scale types, check the Flex LiveDocs page here), and optionally set the printasBitmap
option. I wanted my items to print as vector, and in fact this setting was key to my content printing correctly.
printJob.printAsBitmap=false;
Step 9: Add Code to Execute Before the printJob
Add any alerts or actions you want to perform upon printing (such as an Alert to tell the user to turn on LANDSCAPE printing). For Example:
Alert.show ("Please set your printer settings to LANDSCAPE " + '\n' + "or your quiz will NOT print correctly!")
If you want to set an Alert
, it must execute before your printing begins. Please look at the code provided in the download assocated with this tutorial for the printAlert()
in the main.mxml file.
Step 10: Set Your Printing width
and height
Variables
Set your before printing width and height values for the PrintAdvancedDataGrid
width
, and rowHeight
var before_widdy = printColumn.width; var before_hiddy = printGrid.rowHeight; var widdy = printJob.pageWidth; var hiddy = printJob.pageHeight;
Step 11: Set the New Values
Set the new values for the height
, width
, and rowHeight
to equal the FlexPrintJob
values
printColumn.width = widdy; printGrid.width = widdy; printGrid.rowHeight = hiddy;
Step 12: Add Multiple Pages Code to FlexPrintJob
Add your code to accomodate adding multiple pages to the printJob
. According to the Flex LiveDocs, this nextPage()
check is supposed to ensure that your content will print correctly regardless of your AdvancedDataGrid
height, although I found that after a height of approximately 7500, the printing fails to complete correctly.
while(true) { printGrid.nextPage(); if(!printGrid.validNextPage){ printJob.addObject(printGrid); break; } }
Step 13: Optional Adjust the printGrid
Height
I found that I got fewer printing errors when I added this to the code from Step 12:
printGrid.height = printGrid.measuredHeight; printGrid.verticalScrollPolicy = "off";
So now my total code for Step 12 looks like this:
while(true) { printGrid.nextPage(); if(!printGrid.validNextPage){ printGrid.height = printGrid.measuredHeight; printGrid.verticalScrollPolicy = "off"; printJob.addObject(printGrid); break; } }
This seems to have to do with Flex 3's problem or bug regarding printing datagrids with a large height, which is a real problem for printing things like multiple SWFs or images, because your datagrid height must in fact reflect the total height of the images or SWFs being printed!
Step 14: Add the Code to add Anything Else to Your printJob
printJob.addObject(printGrid2); printJob.addObject(swfLoader, FlexPrintJobScaleType.NONE);
Step 15: Send Your printJob
Close your printJob
, and perform any additional functions such as an Alert
, and resetting the printGrid
and printColumn
values back to what they were before printing. If you are using a view State for printing, where the user has perhaps a Print Center type interface where they can see what they are about to print, this would be useful. If you choose to add the PrintAdvancedDataGrid
to the stage via actionscript, then this would be unnecessary, and in this step you would instead remove the PrintAdvancedDataGrid
from the stage.
printJob.send(); printColumn.width = before_widdy; printGrid.width = before_widdy; printGrid.rowHeight = before_hiddy; Alert.show("Print Job Complete!")
Step 16: Add Another PrintAdvancedDataGrid
if Your Content Exceeds 7500 Height
<mx:PrintAdvancedDataGrid paddingTop="0" paddingBottom="0" visible="true" rowHeight="200" sizeToPage="true" showHeaders="false" id="printGrid2" creationComplete="stripQuiz2();" height="{grid_height}" width="300"> <mx:columns> <mx:AdvancedDataGridColumn width="200" id="printColumn2" itemRenderer="com.reiman.PrintItemRenderer" sortable="false" /> </mx:columns> </mx:PrintAdvancedDataGrid>
Step 17: Duplicate Your dataProvider
and resultHandler
My data call looked like this originally:
<mx:HTTPService id="httpService" url="{myUrl}" resultFormat="object" result="httpResult_handler(event)" fault="Alert.show('data load error')" />
All I needed to do was add an additional result handler, so that the data for the second PrintAdvancedDatagrid
would be separate:
<mx:HTTPService id="httpService" url="{myUrl}" resultFormat="object" result="httpResult_handler(event);httpResult_handler2(event)" fault="Alert.show('data load error')" />
Then I added a new result handler:
private function httpResult_handler2(evt:ResultEvent):void { if (evt.result.lessons.row) { var resultAC:ArrayCollection = evt.result.lessons.row as ArrayCollection; for (var i:int=0;i<resultAC.length;i++) { var row:Row = new Row(); row.fill(resultAC[i]); lessonsDataProvider2.addItem(row); } getSelectedItem() }
This new dataProvider
for the second PrintAdvancedDatagrid
is set in my stripQuiz2()
filter function.
Step 18: Add collectionView
Variables and Additional dataProvider
In my case, I needed to add 3 new ListCollectionView
s, and 1 more dataProvider
:
[Bindable]private var removeQuiz:ListCollectionView = new ListCollectionView(); [Bindable]private var removeQuiz2:ListCollectionView = new ListCollectionView(); [Bindable]private var itemsQuiz:ListCollectionView = new ListCollectionView(); [Bindable]private var lessonsDataProvider2:ArrayCollection = new ArrayCollection();
Step 19: Add filterFunction
Functions
For the two datagrids I wished to print from, I needed filters that would perform the following functions:
- Strip out certain activity types from my printing list, as some were not necessary
- Truncate the data at 12 entries, as after extensive testing I found that this was the limit for successfully printing full page SWFs from the datagrid
- Remove the first 12 entries from the
ListCollectionView
for the 2ndPrintAdvancedDatagrid
This resulted in the creation of the following functions:
private function stripQuiz():void { removeQuiz = new ListCollectionView(lessonsDataProvider); removeQuiz.filterFunction = quizStripped; removeQuiz.refresh(); quizLimit(); removeQuiz.refresh(); printGrid.dataProvider = removeQuiz; } private function stripQuiz2():void { removeQuiz2 = new ListCollectionView(lessonsDataProvider2); removeQuiz2.filterFunction = quizStripped; removeQuiz2.refresh(); quizPage2(); removeQuiz2.refresh(); printGrid2.dataProvider = removeQuiz2; } private function quizStripped(value:Object):Boolean { return String(value.lessonIcon).toUpperCase() != "QUIZ" && String(value.lessonIcon).toUpperCase() != "HANGMAN" && String(value.lessonIcon).toUpperCase() != "FLASHCARDS" && String(value.lessonIcon).toUpperCase() != "DOWNLOADS" && String(value.lessonIcon).toUpperCase() != "VIDEO" && String(value.fileName).toUpperCase() != "ASSETS/ACTIVITIES/WORKSHEET_KIDS_AUDIO.SWF" && String(value.fileName).toUpperCase() != "ASSETS/ACTIVITIES/WORKSHEET_KIDS_AUDIO5.SWF"; } private function quizLimit():void { for (var i:int=removeQuiz.length-1; i>=0; i--) { if (removeQuiz.length >= 13) removeQuiz.removeItemAt(i); } } private function quizPage2():void { if (removeQuiz2.length >= 13) removeQuiz2.removeItemAt(12); removeQuiz2.removeItemAt(11); removeQuiz2.removeItemAt(10); removeQuiz2.removeItemAt(9); removeQuiz2.removeItemAt(8); removeQuiz2.removeItemAt(7); removeQuiz2.removeItemAt(6); removeQuiz2.removeItemAt(5); removeQuiz2.removeItemAt(4); removeQuiz2.removeItemAt(3); removeQuiz2.removeItemAt(2); removeQuiz2.removeItemAt(1); }
Step 20: Add Second printGrid
to printJob
Before sending the printJob, I added the second PrintAdvancedDatagrid
to be printed:
printJob.addObject(printGrid2); printJob.send();
For a better look at the complete code, please download the source files associated with this tutorial and take a look at the main.mxml file.
Conclusion
This might not be the most elegant solution to bulk printing SWFs from Flex, but it is a solution that I found works reliably, with high quality output. Printing in Flex can be a real bear, but that doesn't mean you should overlook the options that are available to you, in particular through the use of PrintDatagrid
and PrintAdvancedDatagrid
. Thanks very much for reading my tutorial, and I look forward to any questions and comments you may have.
Comments