Introduction
In this article, I will explain how a Silverlight application can be used to create slides in a Microsoft PowerPoint file by taking various snaps of the Silverlight page. I was motivated to write this because I did not see a solution for this in the internet when I wanted to create PowerPoint slides from a Silverlight project, but did find many results on using Silverlight in PowerPoint.
How it works
The solution has three projects:
- PowerpointService (a WCF Service project)
- SilverlightToPowerpoint (a Silverlight project)
- SilverlightToPowerpoint.Web (a web application hosting the Silverlight project)
The Silverlight project makes a call to the WCF Service by sending snaps of pages (byte array as image) with a title. In return, WCF creates PowerPoint slides and sends them back as a byte array. The Silverlight app prompts the Save dialog box to the user to decide where to save the file and saves the byte array as a Microsoft PowerPoint file.
The WCF Service becomes necessary because we want to manipulate a file which is not supported in Silverlight as it runs in the client's browser and accessing a file is denied. (I hear some of you say "Hey! there is Isolated Storage", but for this to work, that is not an ideal solution.)
Third party libraries
Third party libraries are used to take a snap of Silverlight and create slides in Microsoft PowerPoint.
For taking a snap of Silverlight, the following libraries are used in the Silverlight project:
- ImageTools.dll
- ImageTools.IO.Png.dll
- ICSharpCode.SharpZipLib.Silverlight.dll
These are free libraries and can be downloaded from CodePlex.
For creating slides, DocumentFormat.OpenXml.dll is used in the WCF Service project. This library can be downloaded from OpenXmlDeveloper.
Note: You really do not need to download these libraries as they are part of the solution attached here. The links are for your further reference only.
Screenshots
In the Silverlight project, I have used three images of three cars, but these images are stored in the web project under the ClientBin->Images folder to reduce the size of the Silverlight project.
The first page when you run the Silverlight app is:
After selecting a car:
After adding to the slide collection (by default, the car name is shown as the title, but this can be changed):
Taking multiple cars (in the code, I've set the maximum as 20, but you can change this):
Save the slides into a PowerPoint file:
Here is the confirmation:
...And the PowerPoint file:
Code walkthrough
Silverlight project
The Silverlight project has a user control file MainPage.xaml where all the UI design is written. Though the .xaml is self-explanatory and easy to understand for any Silverlight developer, the below markup code requires special explanation:
<stackpanel name="spSlide" grid.row="1">
<img name="imgCar" stretch="UniformToFill" />
</stackpanel>
This StackPanel
is very important as, in the code, I convert the StackPanel
and its child controls to an image and passes it to the WCF Service. Here I have just used an image, but if you want to add more controls, you must add them under the StackPanel
. Of course, in the code I have used a StackPanel
, but you can use any other container control for that matter.
This is the code that gets executed when you click Add to Slide.
if (cbCars.SelectedValue == null) return;
if (_slides.Count > 20)
throw new Exception("Maximum slides (20) added already!");
WriteableBitmap image = new WriteableBitmap(spSlide, new ScaleTransform());
try
{
PptSlide slide = new PptSlide();
byte[] binaryData = GetImageBufferAsPng(image);
BitmapImage bmpImage = new BitmapImage();
Stream stream = new MemoryStream(binaryData, 0, binaryData.Length);
bmpImage.SetSource(stream);
slide.Image = bmpImage;
slide.ImageBinary = binaryData;
slide.ImageTitle = string.Format("{0}_{1}",
((ComboBoxItem)cbCars.SelectedItem).Content, _slides.Count + 1);
_slides.Add(slide);
tabSlides.Header = string.Format("Slides ({0})", _slides.Count);
}
catch (Exception ex)
{
ShowErrorAlert(string.Format(
"Error while adding to slide collection:\n{0}", ex.Message));
}
This code uses the Image libraries to convert the StackPanel
and its controls to a PNG image and adds it to the internal observable collection which is the source of the DataGrid
.
The code gets executed when you click Save as PowerPoint:
if (_slides.Count == 0) return;
try
{
var service = new PowerpointClient();
var slides = new List<powerpointimage>();
foreach (var item in _slides)
{
var slide = new PowerpointImage();
slide.ByteArray = item.ImageBinary;
slide.Title = item.ImageTitle;
slides.Add(slide);
}
SaveFileDialog = new SaveFileDialog();
SaveFileDialog.DefaultExt = "Powerpoint 2007 (*.pptx)|*.pptx";
SaveFileDialog.Filter = "Powerpoint 2007 (*.pptx)|*.pptx";
SaveFileDialog.FilterIndex = 1;
if (SaveFileDialog.ShowDialog() == true)
{
service.PowerpointAsByteCompleted +=
service_PowerpointAsByteCompleted;
service.PowerpointAsByteAsync(slides);
tbStatus.Text = "Saving file...";
}
}
catch (Exception ex)
{
ShowErrorAlert(string.Format(
"Error while getting powerpoint file:\n{0}", ex.Message));
}
...on completion of the WCF service call:
void service_PowerpointAsByteCompleted(object sender,
PowerpointAsByteCompletedEventArgs e)
{
if (e.Error != null)
{
ShowErrorAlert(string.Format(
"Error while getting powerpoint file:\n{0}",
e.Error.Message));
return;
}
if (!string.IsNullOrWhiteSpace(e.Result.ErrorMessage))
{
ShowErrorAlert(e.Result.ErrorMessage);
return;
}
byte[] pptByte = e.Result.PptByte;
if (pptByte == null)
{
ShowErrorAlert("Empty powerpoint file is returned from server.");
return;
}
using (System.IO.Stream stream = SaveFileDialog.OpenFile())
{
stream.Write(pptByte, 0, pptByte.Length);
tbStatus.Text = "File saved: " + SaveFileDialog.SafeFileName;
}
}
WCF project
This project offers only one method PowerpointAsByte
. And the service interface IPowerpoint
:
[ServiceContract]
public interface IPowerpoint
{
[OperationContract]
PowerpointAsByteResult PowerpointAsByte(IList<powerpointimage> images);
}
The service class Powerpoint
:
public class Powerpoint : IPowerpoint
{
public PowerpointAsByteResult PowerpointAsByte(IList<powerpointimage> slides)
{
PowerpointAsByteResult result = new PowerpointAsByteResult();
try
{
result.PptByte = (new PowerpointHelper()).GetPowerpointAsByte(slides);
}
catch (Exception ex)
{
result.ErrorMessage = string.Format(
"Error while converting images to powerpoint:\n{0}",ex.Message);
}
return result;
}
}
Class diagram of the WCF Service
Somethings to remember before working with the code
- You need Visual Studio 2010 to open this code.
- When you run the project, you may receive an error message when making a WCF call. This is because the address of the WCF Service may change in your development server. So first run the WCF Service and take the address and update it in the ServiceReferences.ClientConfig file of the Silverlight project.
- If you are dealing with images of huge size, then you need to increase the upload size limit in the web.config file. Have a look at this link: http://forums.silverlight.net/forums/p/65327/433543.aspx.
Conclusion
I believe this article will help at least a few people who need this functionality. Please test this code and post your comments on any issues/suggestions.