One of the features most requested by the Silverlight community has been client-side printing support. Silverlight 4 includes a new printing API that allows developers to enable client printing from a Silverlight application. In this chapter, we will explore the new printing API and run through some exercises to see printing in action.
The primary class that controls printing from a Silverlight application is the PrintDocument
class. Let's take a look at the PrintDocument
class and its members.
The action of opening the print dialog box and printing is initiated by the Print
method. This method triggers three events in the following order: The BeginPrint
event is fired when the print dialog box displays successfully and the user selects print. Once the printing process begins, the PrintPage
event is fired as each page prints. The EndPrint
event is fired when the printing process is complete or when the printing job has been cancelled by the user. If there was an error while printing, the Error
property of the EndPrintEventArgs
can be inspected.
When the PrintPage
event fires, what will be printed is determined by the PrintPageEventArgs.PageVisual
property. You can either set the PageVisual
property to a UIElement
contained within your XAML content, or you can construct your own XAML content dynamically and set that content to the PageVisual
. Let's walk through two exercises, one for each of these options.
In this example we'll create a very simple Silverlight application with a DataGrid that displays contacts, and we'll add printing functionality.
Create a new Silverlight application in Visual Studio 2010. Name it SimplePrinting
and allow Visual Studio to create an ASP.NET web application called SimplePrinting.Web
.
When the project is created, you should be looking at the MainPage.xaml
file. Change the LayoutRoot
item to be a StackPanel
, add a TextBlock
with the Text
property set to "Contacts," and set the FontWeight
property to Bold
. Next, add a DataGrid
named ContactGrid
. Note that for the DataGrid
you must have a reference to the Silverlight SDK as explained in Chapter 5. Below the DataGrid
add a nested horizontal StackPanel
containing two buttons. The content should be "Print As-Is" for the first button and "Print Formatted" for the second.
<StackPanel x:Name="LayoutRoot" Background="White"><TextBlock Text="Contacts" FontWeight="Bold" />
<sdk:DataGrid Name="ContactGrid" />
<StackPanel Orientation="Horizontal">
<Button Content="Print As-Is" />
<Button Content="Print Formatted" />
</StackPanel>
</StackPanel>
Next let's add some styles to improve the look of the application. We will add three implicit styles (as discussed in Chapter 12).
<UserControl x:Class="SimplePrinting.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"><UserControl.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="5" />
</Style>
<Style TargetType="sdk:DataGrid">
<Setter Property="Margin" Value="5" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5" />
<Setter Property="FontSize" Value="18" />
</Style>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="White"> <TextBlock Text="Contacts" FontWeight="Bold" /> <sdk:DataGrid Name="ContactGrid" /> <StackPanel Orientation="Horizontal"> <Button Content="Print As-Is" /> <Button Content="Print Formatted" /> </StackPanel> </StackPanel> </UserControl>
At this point, the application should look like what's shown in Figure 15-1.
Next we need to wire up an event handler for each button's Click
event. First we'll set the Click
event in the XAML. We will name the delegates PrintAsIs
and PrintFormatted
.
<StackPanel x:Name="LayoutRoot" Background="White"> <TextBlock Text="Contacts" FontWeight="Bold" /> <sdk:DataGrid Name="ContactGrid" /> <StackPanel Orientation="Horizontal"> <Button Content="Print As-Is" Click="PrintAsIs" /> <Button Content="Print Formatted" Click="PrintFormatted" /> </StackPanel> </StackPanel>
Next we'll make certain the two event handlers are present in the code behind.
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { }private void PrintAsIs(object sender, RoutedEventArgs e)
{
}
private void PrintFormatted(object sender, RoutedEventArgs e)
{
}
}
Now we need to define the data that we'll bind to our ContactGrid
. First, create a simple class called Contact
that contains four simple string properties: Name, Address, CityStateZip
, and Phone
.
public class Contact { public string Name { get; set; } public string Address{ get; set; } public string CityStateZip{ get; set; } public string Phone{ get; set; } }
After the Contact
class has been defined, we need to add the actual data to the DataGrid
. We will do this in the Loaded
event, so first we need to create a delegate to handle the event. Then we can add our data.
public partial class MainPage : UserControl {List<Contact> Contacts;
public MainPage() { InitializeComponent();this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Contacts = new List<Contact>();
Contacts.Add(new Contact() {
Name = "John Doe",
Address = "123 Driveway Road",
CityStateZip = "SomeCity, OH 12345",
Phone = "(123) 456-7890"
});
Contacts.Add(new Contact()
{
Name = "Jane Doe",
Address = "456 Windy Road",
CityStateZip = "Cityville, FL 34566",
Phone = "(111) 222-3333"
});
ContactGrid.ItemsSource = Contacts;
}
private void Button_Click(object sender, RoutedEventArgs e) { } private void PrintAsIs(object sender, RoutedEventArgs e) { } private void PrintFormatted(object sender, RoutedEventArgs e) { } } public class Contact { public string Name { get; set; } public string Address{ get; set; } public string CityStateZip{ get; set; } public string Phone{ get; set; } }
Let's test the application now to make sure we have the data binding properly. Press F5 to debug the application; it should appear as shown in Figure 15-2.
Now we need to add the printing logic to our application. In this example, we'll simply print what we see on the screen by setting the PageVisual
to the LayoutRoot
. To start, create an instance of the PrintDocument
object in the PrintAsIs
delegate. The PrintDocument
object belongs to the System.Windows.Printing
namespace, so you'll need to add the using System.Windows.Printing
statement to the top of the MainPage.xaml.cs
file, as shown in Figure 15-3.
Next we'll wire up the PrintPage
event. We can do this by defining a separate delegate, or we can use a lambda expression to define the delegate logic inline. In this example we'll use the latter.
private void PrintAsIs(object sender, RoutedEventArgs e) { PrintDocument doc = new PrintDocument();doc.PrintPage += (s, args) =>
{
};
}
After wiring up the PrintPage
event, call the Print()
method, which essentially calls the PrintPage
logic. The Print()
method requires a document name be passed, so pass in "As Is" as the name of the printed document.
private void PrintAsIs(object sender, RoutedEventArgs e)
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, args) =>
{
};
doc.Print("As Is");
}
Now we just need to add the logic to our PrintPage
lambda expression. Since we're just printing the content as we see it on the screen, we simply set the PageVisual
property to the LayoutRoot
to tell Silverlight to print all of the XAML content contained in the application. The PageVisual
property belongs to the PrintPageEventArgs
class and is passed into the PrintPage
event delegate.
private void PrintAsIs(object sender, RoutedEventArgs e)
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, args) =>
{
args.PageVisual = LayoutRoot;
};
doc.Print("As Is");
}
Press F5 now to test the application. When the application is displayed, press the "Print As-Is" button, which will display the Print dialog as shown in Figure 15-4.
Select the desired printer and press Print. If all goes well, the content that is printed should be just as you see on the screen in Figure 15-5.
It may not always be ideal to print application content just as displayed on the screen. Fortunately, however, you can print custom content. Because you have to set the PageVisual property in order to print, you can simply set that to whatever content you'd like, including dynamically created content.
We will continue working from the SimplePrinting
project we created in the previous section. In the PrintFormatted
delegate, add a new instance of the PrintDocument
class, wire up the PrintPage
event, and call the Print
method.
private void PrintFormatted(object sender, RoutedEventArgs e) {PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, args) =>
{
};
doc.Print("Formatted Print");
}
Within the PrintPage
event logic, we now need to wire up the PageVisual
property. In the previous example, we simply set this to the LayoutRoot
element, but in this case we want to customize the printed content. To do this we'll dynamically create content to set to the PageVisual
property. We will create a StackPanel
at runtime and add content to that StackPanel
for each Contact
in our Contacts
collection.
Add an instance of a StackPanel
called customPrintPanel
and then add a foreach
statement that will step through each Contact
in the Contacts
collection. Then, within the foreach
, create another StackPanel
to contain the Contact
information. Now add a Margin
of 25 to surround the contact panel to prevent the content from appearing too close to the left margin of the printed page, as well as to keep the contacts from all stacking up together. Next insert the logic to add the contact panel to the customPrintPanel
.
Finally, outside the foreach
set the PageVisual
to the customPrintPanel
.
private void PrintFormatted(object sender, RoutedEventArgs e) { PrintDocument doc = new PrintDocument(); doc.PrintPage += (s, args) => {StackPanel customPrintPanel = new StackPanel();
foreach (Contact c in Contacts)
{
StackPanel contactPanel = new StackPanel();
contactPanel.Margin = new Thickness(25);
customPrintPanel.Children.Add(contactPanel);
}
args.PageVisual = customPrintPanel;
}; doc.Print("Formatted Print"); }
Next we need to add the contact information to the contact panel. In this example, we'll simply add a TextBlock
for each of the contact attributes to display a plain text value.
private void PrintFormatted(object sender, RoutedEventArgs e) { PrintDocument doc = new PrintDocument(); doc.PrintPage += (s, args) => { StackPanel customPrintPanel = new StackPanel();
foreach (Contact c in Contacts) { StackPanel contactPanel = new StackPanel(); contactPanel.Margin = new Thickness(25);TextBlock name = new TextBlock();
name.Text = c.Name;
contactPanel.Children.Add(name);
TextBlock address = new TextBlock();
address.Text = c.Address;
contactPanel.Children.Add(address);
TextBlock city = new TextBlock();
city.Text = c.CityStateZip;
contactPanel.Children.Add(city);
TextBlock phone = new TextBlock();
phone.Text = c.Phone;
contactPanel.Children.Add(phone);
customPrintPanel.Children.Add(contactPanel); } args.PageVisual = customPrintPanel; }; doc.Print("Formatted Print"); }
Now press F5 to test the application. When the application shows up, press the Print Formatted button; when you see the Print dialog, select your printer and press Print. If all goes well, the printed output should appear as shown in Figure 15-6.
There may be times when you need even more control over the printing process. One such situation would be if you wanted to provide a status notification to the user to indicate when the printing process is taking place, when it completes, and whether it was successful. Earlier in this chapter we discussed two events, BeginPrint
and EndPrint
. You can use these events to create code that runs before and after the printing process takes place.
We'll continue with the example we've been working with throughout this chapter and add handling for the BeginPrint
and EndPrint
events in order to display a message to the user about the status of the printing process.
Let's keep working with the SimplePrinting
project we created earlier. Start by opening the XAML for MainPage.xaml
.
Add a new TextBlock
to the LayoutRoot StackPanel
below the panel holding the buttons. Set the Foreground
color to Red
and the FontWeight
to Bold
. Name the TextBlock PrintStatus
.
<StackPanel x:Name="LayoutRoot" Background="White">
<TextBlock Text="Contacts" FontWeight="Bold" />
<sdk:DataGrid Name="ContactGrid" />
<StackPanel Orientation="Horizontal">
<Button Content="Print As-Is" Click="PrintAsIs" />
<Button Content="Print Formatted" Click="PrintFormatted" />
</StackPanel>
<TextBlock Foreground="Red" FontWeight="Bold" Text="" Name="PrintStatus" />
</StackPanel>
Move to the MainPage.xaml.cs
file. We are going to add to our Print Formatted functionality, so our coding will take place within the PrintFormatted
event (the click event we added to our button). Below the PrintDocument
instantiation, we'll add two lambda expressions to handle the BeginPrint
and EndPrint
events.
private void PrintFormatted(object sender, RoutedEventArgs e) { PrintDocument doc = new PrintDocument();doc.BeginPrint += (s, args) =>
{
};
doc.EndPrint += (s, args) =>
{
};
doc.PrintPage += (s, args) => { StackPanel customPrintPanel = new StackPanel(); foreach (Contact c in Contacts) { StackPanel contactPanel = new StackPanel(); contactPanel.Margin = new Thickness(25); TextBlock name = new TextBlock(); name.Text = c.Name; contactPanel.Children.Add(name); TextBlock address = new TextBlock(); address.Text = c.Address; contactPanel.Children.Add(address); TextBlock city = new TextBlock(); city.Text = c.CityStateZip; contactPanel.Children.Add(city);
TextBlock phone = new TextBlock(); phone.Text = c.Phone; contactPanel.Children.Add(phone); customPrintPanel.Children.Add(contactPanel); } args.PageVisual = customPrintPanel; }; doc.Print("Formatted Print"); }
Next we'll add code to these two lambda expressions. In the BeginPrint
event we are going to change the Text
of the PrintStatus TextBlock
we added to "Printing..." so the user can see when the printing process began. In the EndPrint
event we will concatenate the phrase "Printing Finished!" to the end of the PrintStatus TextBlock
. This will tell the user when the printing process is complete.
private void PrintFormatted(object sender, RoutedEventArgs e) { PrintDocument doc = new PrintDocument();doc.BeginPrint += (s, args) =>
{
PrintStatus.Text = "Printing...";
};
doc.EndPrint += (s, args) =>
{
PrintStatus.Text += "Printing Finished!";
};
... }
Now press F5 to test the application. When the application opens, click on the Print Formatted button. When the print dialog opens, select your printer and press Print. You'll see the status text displayed as shown in Figure 15-7.
In this chapter, we looked at the Silverlight printing API. We saw how to easily print content as it appears on the screen, as well as how to print custom content. As you saw, the new printing API lets you add rich printing functionality to your Silverlight applications. In the next chapter we will take a look at deploying Silverlight applications.