Create PDF ?

8778
76
07-20-2010 06:02 AM
CraigPerreault
New Contributor II
Does anyone have a code sample to create a PDF file using the Silverlight API?
0 Kudos
76 Replies
DavidFlack
New Contributor III
Great tool Kevin!  It worked for me pretty quickly even though I have very limited knowledge of Server, C#, and Silverlight.

I did find an unhandled exception when I accidentally left a PDF open and went to create one with the same file name/location.  Nothing serious- just something you may want to consider for a new version.  Thanks again.
0 Kudos
DarinaTchountcheva
Occasional Contributor II
Kevin, thank  you for sharing this great tool with us. Awsome!

David, I have encountered the same exception, and fixing it wasn't as straight forward as catching the error, so I have decided to share my solution and the sources I have used. Maybe someone will come with a better one.

There seems to be a bug in Silverlight's SaveFileStream, which is discussed in greater details here:

http://betaforums.silverlight.net/forums/p/176869/440957.aspx

I have also noticed that silverPDF is a pretty big library. My .xap jumped to 990 KB from 140 KB just by adding it as reference. And of course this affects the loading time of the application. To avoid this I have implemented a dynamic loading on demand of this library when the user decides to create a PDF. Here is the article I have followed:

http://msdn.microsoft.com/en-us/library/cc903931(VS.95).aspx

And this is my final code:

#region PDF creation

        AssemblyPart apSilverPDF;
        System.IO.Stream PDFStream;


        /// <summary>
        /// Handle "Create PDF" button click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LoadPDFLibraryAndSaveFile();
        }
        
        /// <summary>
        /// Load the silverPDF library if it hasn't been loaded already
        /// or use the library if already loaded        
        /// </summary>
        private void LoadPDFLibraryAndSaveFile()
        {
            SaveFileDialog savePDF = new SaveFileDialog();
            savePDF.Filter = "PDF file format|*.pdf";
            savePDF.DefaultExt = ".pdf";
            if (savePDF.ShowDialog() == true)
            {
                //catch the error if the user tries to save over a file that is currently open
                try                
                {
                    PDFStream = savePDF.OpenFile();
                    //if silverPDF library is not loaded already, load it
                    if (apSilverPDF == null)
                    {
                        LoadPDFLibrary();
                    }
                    else
                    {
                        //if the library is loaded, just save the PDF file
                        SavePDF();
                    }
                }
                catch (IOException ioe)
                {
                    //tell the user the file is open
                    MessageBox.Show("Selected PDF file is open. Close the file and try again!", "Open File Error", MessageBoxButton.OK);
                }                
            }
        }

        /// <summary>
        /// Send a request to load the silverPDF library dynamically
        /// </summary>
        private void LoadPDFLibrary()
        {
            WebClient client = new WebClient();
            client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
            client.OpenReadAsync(new Uri("silverPDF.dll", UriKind.Relative));
        }

        /// <summary>
        /// Load the library and use it to create the PDF
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if ((e.Error == null) && (e.Cancelled == false))
            {
                apSilverPDF = new AssemblyPart();
                apSilverPDF.Load(e.Result);

                SavePDF();
            }
            else
            {
                MessageBox.Show("There was a problem loading PDF Library", "PDF Library Loading Error", MessageBoxButton.OK);
            }
        }

        
        /// <summary>
        /// Save the PDF File
        /// </summary>
        private void SavePDF()
        {
            PDFExporter PDFNew = new PDFExporter();
            //add map to the PDF
            PDFNew.SetMap(Map);
            //add navigation with scale bar to the PDF
            PDFNew.AddElement(navigationGrid);            
            PDFNew.SetOutputStream(PDFStream);
            PDFNew.DoExport();
        }

        #endregion


And in App.xaml.cs:

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            // If the app is running outside of the debugger then report the exception using
            // the browser's exception mechanism. On IE this will display it a yellow alert 
            // icon in the status bar and Firefox will display a script error.
            if (!System.Diagnostics.Debugger.IsAttached)
            {

                // NOTE: This will allow the application to continue running after an exception has been thrown
                // but not handled. 
                // For production applications this error handling should be replaced with something that will 
                // report the error to the website and stop the application.
                e.Handled = true;

                //This is required to swallow an error when saving a file over a file that is open.
                //This is a bug, and application crashes if this line is not there.
                if (e.ExceptionObject.StackTrace.IndexOf("SaveFileStream") != -1) return;
                Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
            }
        }
0 Kudos
MikkelHylden
Occasional Contributor
I have this all working pretty well, except for the Image Service layer in my map.  That throws the dreaded "Catastrophic Failure" message, and then I just get the last layer added to my map.  If I leave out the image service layer from the map, I can get the image as expected (visible layers as on map).

Does anyone have a way to successfully get an image service layer to be incorporated in this solution?  I have already modified the URI section to not include the format (since f=image is not a valid format for the image server service export function).  I tried passing BMP and PNG instead, but those don't work either.
0 Kudos
MarkCederholm
Occasional Contributor III
Actually, ArcGIS image server does support the f=image option.  The whole point of the PDF exporter class is that it depends upon services being able to stream bytes directly to the client.  It works with the image service I use just fine.
0 Kudos
MarkCederholm
Occasional Contributor III
Whoops, I take back what I said.  I found a couple of bugs after all in the uploaded version.  I've just uploaded a new version (1.1) with these fixes:  1) Uses "exportImage" rather than "export" for image services, 2) Sets bboxSR for layers with a different SR than the map.

I've started thinking about the best way to deal with stale output stream handles, and may set up an optional two-stage export process.
0 Kudos
DavidFlack
New Contributor III
Thanks Darina- that try/catch block did the trick.  I wasn't able to get the dynamic loading figured out that this point, but I'll probably get back to that someday.  My app will get fairly heavy printing use, so I don't think it's terrible to pre-load it in this case.

For others, my code is as follows

       private void Print(object sender, RoutedEventArgs e)
        {
            SaveFileDialog savePDF = new SaveFileDialog();
            savePDF.Filter = "PDF file format|*.pdf";
            savePDF.DefaultExt = ".pdf";
            if (savePDF.ShowDialog() == true)
            {
                try
                {
                    System.IO.Stream PDFstream = savePDF.OpenFile();
                    PDFExporter PDFNew = new PDFExporter();
                    PDFNew.SetMap(MyMap);
                    PDFNew.SetOutputStream(PDFstream);
                    PDFNew.DoExport();
                }
                catch (IOException ioe)
                {
                    //tell user the file is open
                    MessageBox.Show("Selected PDF file is open. Close file and try again!", "Open File Error", MessageBoxButton.OK);
                }
                
            }
        }
0 Kudos
ErikEngstrom
Occasional Contributor II
I am trying to implement a PDF printing option and have chosen to use the sample code from Mark C. The problem I am having is that I get the "Map Not Set" error in the PDFExporter IntStartExport() catch. So, my map is essentially coming up as null.

My question then, is how do I set my map resource for this?

This is what I did:
First, I downloaded the SilverPDF DLL and added it to my project as a reference.
I then added a UserControl (ExportPDF.XAML) to my application and used the code from Mark C's download. I also added the PDFExporter.cs to my project (both the user control and the PDFExporter are in the root of the project). I created a namespace to my project and call the usercontrol from the namespace, ie:
<showTest:ExportPDF x:Name="whatever" >
</showTest:ExportPDF>

There doesn't seem to be a Map="" property available to bind the map to the user control.
I've played with this from PDFExporter.cs:
        
public void SetMap(Map TheMap)
    {
        _Map = TheMap;
    }


And this from ExportPDF.xaml.cs:
        public void SetMap(Map TheMap)
        {
            _Exporter.SetMap(TheMap);
        }


For reference my project is named "showTest" and my map element name is "MyMap"
I'm no code junky, so I apologize for not being able to get too specific. I also will not claim that the answer is not totally obvious, because it probably is...

Thanks for any and all help!!!!
0 Kudos
DouglasGennetten
New Contributor III
Is anyone doing PDF exporting from the WPF API? Any examples?
0 Kudos
victorruiz
New Contributor
I had this problem too, with bitmap conversion, so I'm using this.

1. First I changed Bitmap2Stream to this

private void Bitmap2Stream()
    {
// Encode to stream        
       
        int hgt = _wbm.PixelHeight;
        int wdth = _wbm.PixelWidth;
        EditableImage ei = new EditableImage(wdth, hgt);

        for (int y = 0; y < hgt; y++)
        {
            for (int x = 0; x < wdth; x++)
            {
                int pixel = _wbm.Pixels[((y * wdth) + x)];
                ei.SetPixel(x, y, (byte)((pixel >> 16) & 0xff), (byte)((pixel >> 😎 & 0xff), (byte)(pixel & 0xff), (byte)((pixel >> 24) & 0xff));
            }
        }

        //Get the stream from the encoder and create byte array from it
        System.IO.Stream pngStream = ei.GetStream();
        _stream = pngStream;
        _wbm = null;
}

The EditableImage  class you can get it from the attachment file, I can't remember the Url where I got this files sorry ='C.

2 Second you must add the "format" parameter with the jpg option in the Layer2Bitmap function
string sData = "?f=image";
sData += "&format=jpg";

Doing this you will get a nice image inside your PDF.

Bye
0 Kudos
ErikEngstrom
Occasional Contributor II
I am trying to implement a PDF printing option and have chosen to use the sample code from Mark C. The problem I am having is that I get the "Map Not Set" error in the PDFExporter IntStartExport() catch. So, my map is essentially coming up as null.

My question then, is how do I set my map resource for this?

This is what I did:
First, I downloaded the SilverPDF DLL and added it to my project as a reference.
I then added a UserControl (ExportPDF.XAML) to my application and used the code from Mark C's download. I also added the PDFExporter.cs to my project (both the user control and the PDFExporter are in the root of the project). I created a namespace to my project and call the usercontrol from the namespace, ie:
<showTest:ExportPDF x:Name="whatever" >
</showTest:ExportPDF>

There doesn't seem to be a Map="" property available to bind the map to the user control.
I've played with this from PDFExporter.cs:
        
public void SetMap(Map TheMap)
    {
        _Map = TheMap;
    }


And this from ExportPDF.xaml.cs:
        public void SetMap(Map TheMap)
        {
            _Exporter.SetMap(TheMap);
        }


For reference my project is named "showTest" and my map element name is "MyMap"
I'm no code junky, so I apologize for not being able to get too specific. I also will not claim that the answer is not totally obvious, because it probably is...

Thanks for any and all help!!!!

I'm still having issues with this... Any advice?
0 Kudos