Up until now we have only dealt with Silverlight itself. Now it's time to take things to a higher scope and deal with the environment of Silverlight. Not every piece of your application can be part of your Silverlight solution. Sometimes you may want to store data on the user's computer, interact with a JavaScript function, or even create an application that runs on the user's computer directly without the need for a browser. In this chapter we are going to deal with all of these things. We are going to discuss all the ways Silverlight can interact with its host platform.
In this chapter we will cover the following topics:
You've just created the perfect LOB application. Everything works great and users are happy. After a while, a few of them come to you with a request that seems quite trivial—they need to print forms and pages off your application. As trivial as this request sounds, up until Silverlight 3 this would have been impossible. Silverlight 3 introduced the WriteableBitmap
class, which allows you to save any element in your application as a bitmap and then print it. Silverlight 4, however, was the first Silverlight release to introduce a real solution to the printing problem, known as the printing API.
The entire printing process in Silverlight relies on an object called PrintDocument
. This object contains three events that cover the entire lifespan of the printing process—BeginPrint
, PrintPage
, and EndPrint
.
The BeginPrint
event is raised as soon as the Print
method gets called on the PrintDocument
object. Then, for each page that needs to be printed, PrintDocument
raises the PrintPage
event. This is the most important handler, as it enables you to build and design the printing area and set if there are any more pages to be printed.
Once Silverlight lays out the content to be printed, a bitmap is rasterized and sent to the printer driver. A note to be taken here is that as Silverlight sends a bitmap to the printer driver, its size might be bigger than expected.
Once the page is sent to the printer driver, Silverlight checks if there are any more pages to be printed. If there are, the PrintPage
event gets raised again. If not, the EndPrint
event is raised.
Other than these three events, the PrintDocument
object exposes a property called DocumentName
, which defines the name of the print job, and HasMorePages
, which is a Boolean type property that can inform whether or not there are more pages to print.
Let's go ahead and print some pages!
To get started, open the Chapter6-PrintingAPI project in Visual Studio 2010 from the downloadable content of the book. The project's UI consists of a simple layout of text and an image and a print button.
To get started with the printing API, we first have to initialize the PrintDocument
object. Switch to MainPage.xaml.cs
and add the following line of code above the MainPage
constructor:
private PrintDocument _pd = new PrintDocument();
As discussed earlier, the most important event of the PrintDocument
object is the PrintPage
event. The handler of this event enables us to set what is going to get printed. Let's add the event handler right now. Add the following line of code inside the MainPage
constructor:
_pd.PrintPage += new EventHandler<PrintPageEventArgs>(_pd_PrintPage);
Inside the _pd.PrintPage
handler, we can access the event arguments of the event. The arguments enable us to specify what we want to print (using the PageVisual
property), get the size and margin of the currently printing page (using the PrintableArea
and PageMargins
properties), and set if we have anything else to print (using the HasMorePages
Boolean property). As we have a single element that we wish to print—the LayoutRoot Grid
element—let's set the event handler as follows:
void _pd_PrintPage(object sender, PrintPageEventArgs e) { e.PageVisual = LayoutRoot; e.HasMorePages = false; }
Now that we have set the visual to print, and told the framework it's the only page we wish to print, all that's left to do is call the Print
method of the PrintDocument
object to actually print the page. Add the following code snippet in your MainPage.xaml.cs
file:
private void btnPrint_Click(object sender, RoutedEventArgs e) { _pd.Print("Sample Silverlight Document"); }
Build and run the application, and you should get the result, as shown in the following screenshot:
Click the Print me! button, and you should be greeted with the familiar print dialog box.
If you print the page, you will get the exact same visual as your application:
Now, this Print me! button has no place in the printed material. What if we want to hide it from the printed version of our page?
This is where the BeginPrint
and EndPrint
events come into play. As mentioned earlier, BeginPrint
happens just before the PrintPage
event is called, and EndPrint
is called as soon as the page is sent to the printer. If we wish to hide the Print me! button from the printed page, all we have to do is hide it in the BeginPrint
handler and show it again on the EndPrint
handler.
Add the following handlers to your code, just below the PrintPage
handler:
_pd.BeginPrint += new EventHandler<BeginPrintEventArgs>(_pd_BeginPrint); _pd.EndPrint += new EventHandler<EndPrintEventArgs>(_pd_EndPrint);
Add the actual handler methods to your code:
void _pd_BeginPrint(object sender, BeginPrintEventArgs e) { btnPrint.Visibility = Visibility.Collapsed; } void _pd_EndPrint(object sender, EndPrintEventArgs e) { btnPrint.Visibility = Visibility.Visible; }
Build and run your application again. Print the page, and you should get the result, as shown in the following screenshot:
To demonstrate how to handle multiple pages with Silverlights printing API, open the Chapter6-MutliPrinting project in Visual Studio 2010.
We are going to focus on the printing part, so the UI and corresponding code have already been written for us. The application is a simple agenda viewer based on a ViewBox
control and binding to an entity.
The application contains the following two entities that the UI binds to:
AgendaEntity:
This is the main entity of the application. It contains the attendee's name, the currently viewed page number, the width of the printing area, and a collection of the MeetingEntity
entities, each of which represent a meeting.MeetingEntity:
This is an entity that represents a meeting. It contains the meeting title, abstract, and time.In addition, the application contains a user control named PrintPage.xaml
. This control will be used as the visual for our printing needs.
To better understand what we are going to print, build and run the application now. You should get the result, as shown in the following screenshot:
By binding the meetings ObservableCollection
to ViewBox
and setting its DataTemplate
, we get a nicely charted list of meetings. While this entire agenda can be printed on one page, for the sake of the example, we will assume only three meetings can fit into a single page. As this is the case, we need to handle multipage printing and set the right page number for the printed page.
First, we call the Print
method of the PrintDocument
object when the user clicks on the Print
button. Add the following line of code to the printBtn_Click
event handler in the MainPage.xaml.cs
file:
_pd.Print("My Agenda");
Next, we need to handle the PrintPage
event. Change the _pd_PrintPage
event handler as follows:
void _pd_PrintPage(object sender, PrintPageEventArgs e) { PrintPage pp = new PrintPage(); ObservableCollection<MeetingEntity> meetings = new ObservableCollection<MeetingEntity>(); while (_rowCounter < _entity.Meetings.Count) { meetings.Add(_entity.Meetings[_rowCounter]); _rowCounter++; if (_rowCounter % 3 == 0) { if (_rowCounter == _entity.Meetings.Count) break; else { e.HasMorePages = true; break; } } } AgendaEntity entity = new AgendaEntity(); entity.Meetings = meetings; entity.AttendeeName = _entity.AttendeeName; entity.PageNumber = _entity.PageNumber; entity.PrintWidth = e.PrintableArea.Width; pp.DataContext = entity; e.PageVisual = pp; _entity.PageNumber++; }
Even though it seems like a lot of code, the logic is pretty simple.
We first create an instance of PrintPage
, which is the user control we have mentioned earlier. The user control contains the same UI as MainPage.xaml
minus the print button. We then create a new ObservableCollection
of the MeetingEntity
items to hold the meetings we wish to print on a particular page. Next, we have a while
loop that checks whether or not the _rowCounter
parameter is less than the total sum of meetings the main entity holds, and if it is less, it adds the meeting to the meetings
collection, increments the _rowCounter
parameter by 1, and checks if it can be divided by 3 without a remainder. If it can, that means we have added three items to the new collection. Then, we check if the counter equals the number of meetings the agenda has. If it does, that means we've finished and we should just break the loop and continue with the rest of the method. If it doesn't, that means we have more to print and, thus, we set the HasMorePages
Boolean property of the event to true
. That will cause the PrintPage
method to run again for the new page that needs to be printed. Finally, we create a new AgendaEntity
instance and set its different properties, such as the collection of items (meetings), attendee name, page number, and the width of the printing area. It is important to note that we progress the page number counter by 1 at the end of the method, so that we can get the correct number for the currently printed page.
Finally, we have to deal with the EndPrint
handler. This handler contains a single line of code that simply resets the PageNumber
property to 1
, as the UI shows all the meetings on one page, no matter how many meetings are shown. Add the following line of code to the _pd_EndPrint
handler:
_entity.PageNumber = 1;
That's it. Handling multiple pages in the printing API can be a bit tricky at first so it's highly recommended you go over the code again, just to make sure you understand what's going under the hood there.
Build and run the application, and click on the Print button. If printed to an XPS, you should get the result, as shown in the following screenshot:
There are a total of two pages, each with three meetings.
You don't have to add the entire LayoutRoot
as the PageVisual
to be printed. You can also specify specific elements, both on and off the screen. For example, if we only want to print a ViewBox
control named vb
, all we have to do is set the PageVisual
property of the PrintPageEventArgs
argument of the PrintPage
handler to the name of the ViewBox
control. In other words, e.PageVisual = vb
.
18.118.2.240