Chapter 15. Printing in Silverlight

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 Printing API

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.

PrintDocument Events

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.

Determining Print Content

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.

Try It Out: Implementing Simple Printing

In this example we'll create a very simple Silverlight application with a DataGrid that displays contacts, and we'll add printing functionality.

  1. 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.

  2. 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>
  3. 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.

    The SimplePrinting application

    Figure 15.1. The SimplePrinting application

  4. 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)
        {
    
        }
    }
  5. 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; }
    }
  6. 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; }
    }
  7. 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.

    The SimplePrinting application with data bound

    Figure 15.2. The SimplePrinting application with data bound

  8. 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.

    Referencing the System.Windows.Printing namespace

    Figure 15.3. Referencing the System.Windows.Printing namespace

  9. 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) =>
            {
    
            };
    }
  10. 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");
    }
  11. 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");
    }
  12. 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.

    Print dialog box for the SimplePrinting application

    Figure 15.4. Print dialog box for the SimplePrinting application

    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.

    Printed output from the SimplePrinting application

    Figure 15.5. Printed output from the SimplePrinting application

Printing Custom Content

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.

Try It Out: Implementing a Custom Print

  1. 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");
    }
  2. 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");
    }
  3. 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");
    }
  4. 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.

    Custom print content

    Figure 15.6. Custom print content

Additional Printing Customization

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.

Try It Out: Handling the BeginPrint and EndPrint Events

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.

  1. Let's keep working with the SimplePrinting project we created earlier. Start by opening the XAML for MainPage.xaml.

  2. 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>
  3. 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");
    }
  4. 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!";
        };
    
        ...
    }
  5. 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.

    Handling the additional print events

    Figure 15.7. Handling the additional print events

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset