Exercise 1: Printing the Schedule on One Page

In this part of the lab, you will add the ability to print an attendee’s schedule for an event. In this first part, the output will all be on a single page. The structure of the code and XAML will be very similar in some respects to what you created in the earlier lab for the schedule planner. But there are a few differences in the detail for printing that warrant building a separate view rather than attempting to print using the existing components.

Create the Printing ViewModel and View

  1. Open the SlEventManager solution in Visual Studio 2010.
  2. In the SlEventManager project’s ViewModels folder add a new class called SchedulePrintViewModel.
    Note:
    This will act as the source data for the XAML file that will ultimately define the layout for the printed document.
  3. Add the following using directive:

    C#

    using System.ComponentModel;
  4. Visual Basic

    Imports System.ComponentModel

  5. Implement SchedulePrintViewModel class like this:

    C#

    public class SchedulePrintViewModel { public string EventTitle { get; set; } public string AttendeeName { get; set; } public ICollectionView Talks { get; set; } public double PrintWidth { get; set; } }

    Visual Basic

    Public Class SchedulePrintViewModel Public Property EventTitle() As String Public Property AttendeeName() As String Public Property Talks() As ICollectionView Public Property PrintWidth() As Double End Class

    Note:
    The PrintWidth property will hold the width of the printable area. We’ll need this to impose a layout constraint, as you’ll see.
  6. In the SlEventManager project’s Views folder, add a new Silverlight User Control called SchedulePrintView.
  7. Add the following Grid to the SchedulePrintView:

    XAML

    <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Text="{Binding Path=EventTitle}" /> <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" > <TextBlock Text="Schedule for " /> <TextBlock Text="{Binding Path=AttendeeName}" /> </StackPanel> <Viewbox VerticalAlignment="Top" StretchDirection="DownOnly" Grid.Row="2" > <ItemsControl Width="{Binding Path=PrintWidth}" ItemsSource="{Binding Path=Talks.Groups}" > <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="20,5"> <Grid.RowDefinitions> <RowDefinition Height="20" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Text="{Binding Path=Name,StringFormat=HH:mm}" /> <ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Items}" > <ItemsControl.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition MaxHeight="40" /> </Grid.RowDefinitions> <TextBlock Text="{Binding Path=TalkTitle}" FontWeight="Bold" /> <TextBlock Grid.Row="1" Text="{Binding Path=TalkAbstract}" TextWrapping="Wrap" /> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Viewbox> </Grid>
    Note:
    This is similar to the markup you created for the schedule planner, but with a few subtle differences. We can’t use tooltips in printed output because tooltips rely on mouse interaction, something that doesn’t work too well on paper. On screen, we use tooltips to show the full abstract, enabling us to truncate in the usual view, but when printing, we need to show the whole abstract up front rather than trimming it. (Also, if you did the optional Fluid UI section in the previous lab, you’ll notice that we’re not using a ListBox any longer because we only added that for the entry and exit animations.)

    Also note that we’ve wrapped the ItemsControl in a Viewbox, a new feature in Silverlight 4. This can enlarge or shrink items to fit the available space. We’ve set the StretchDirection to DownOnly meaning this particular Viewbox will never try to enlarge the content—if it’s smaller than the space available it just won’t fill it. But if there’s too much content, it will be shrunk, to fit it on the page. (In the planner UI we were able to deal with this problem by using a ScrollViewer, but of course that’s another UI element type that’s not a great choice for printed output.)

    The Viewbox is the reason we needed the ViewModel to make the PrintWidth available. Notice that the outer ItemsControl’s Width is constrained to be PrintWidth. Without that, the Viewbox would allow the ItemsControl to grow as wide as it liked—the Viewbox performs unconstrained layout on its children. The result would be that all each talk abstract would occupy a single, possibly very long line—the absence of a constraint would mean the text would never be wrapped because the line of text could grow indefinitely long. The Viewbox would then shrink everything to fit. The result would be tiny text with very long talk abstract lines. By forcing the ItemsControl not to get wider than PrintWidth, word wrapping kicks in, and the Viewbox only does its job once the content becomes too tall to fit.

Hook up the Print Button

  1. Open SchedulePlanner.xaml.
  2. Add a new button to the UI at the top right.
  3. Set its content to Print…
  4. Name the button printButton.
  5. Add a Click event handler for printButton.
    Note:
    There’s a slight challenge to getting this button to behave well in layout. In an earlier lab you added a Canvas that covers the whole UI, to make it possible to remove the context menu when the user clicks outside the menu. Because this Canvas is on top, Visual Studio will add new elements to that when you drop them onto the design surface. But because of how this layout resizes, the button would be better off being in the Grid. You can either edit the XAML manually to put it where you need it, or you could use Blend, which has more sophisticated support for working with multiple overlapping layers of content, and deeply nested containers. Or you could temporarily remove the Canvas while you position the button. Or you could just live with the fact that it won’t appear quite where you want when you resize the UI.
  6. In the SchedulePlanner.xaml.cs or SchedulePlanner.xaml.vb code behind file, add these using directives:

    C#

    using System.Windows.Printing; using System.Windows.Data; using System.ComponentModel;
  7. Visual Basic

    Imports System.Windows.Printing Imports System.Windows.Data Imports System.ComponentModel

Set the Printable Content

  1. In the Click handler you just added, write this code to create a PrintDocument

    C#

    PrintDocument pd = new PrintDocument(); pd.PrintPage += (s, pe) => { SchedulePrintView printView = new SchedulePrintView(); SchedulePrintViewModel printViewModel = new SchedulePrintViewModel { EventTitle = _viewModel.EventTitle, AttendeeName = WebContext.Current.User.FriendlyName, Talks = (new CollectionViewSource { Source = _viewModel.SubscribedTalks, GroupDescriptions = { new PropertyGroupDescription("TalkStartTime") }, SortDescriptions = { new SortDescription("TalkStartTime", ListSortDirection.Ascending) } }).View, PrintWidth = pe.PrintableArea.Width }; printView.DataContext = printViewModel; pe.PageVisual = printView; }; pd.Print("Schedule");

    Visual Basic

    Dim pd As New PrintDocument Dim Template As String Dim ViewSourceTemp As CollectionViewSource = (New CollectionViewSource With {.Source = _viewModel.SubscribedTalks}) ViewSourceTemp.GroupDescriptions.Add(New System.Windows.Data.PropertyGroupDescription("erw")) ViewSourceTemp.SortDescriptions.Add(New System.ComponentModel.SortDescription("TalkStartTime", ListSortDirection.Ascending)) AddHandler pd.PrintPage, Sub(s, pe) Dim printView As New SchedulePrintView Dim printViewModel As SchedulePrintViewModel = New SchedulePrintViewModel With {.EventTitle = _viewModel.EventTitle, .AttendeeName = WebContext.Current.User.FriendlyName, .Talks = ViewSourceTemp.View, .PrintWidth = pe.PrintableArea.Width} printView.DataContext = printViewModel pe.PageVisual = printView End Sub pd.Print("Schedule")
  2. After the PrintPage handler, invoke the Print method of the PrintDocument

    C#

    pd.Print("Schedule");
  3. Visual Basic

    pd.Print("Schedule")
    Note:
    This creates a PrintDocument, and attaches a handler for its PrintPage event. That handler creates the print view and view model.

    We’re creating the CollectionViewSource in code here and putting its View property directly into the view model. We’re doing this because in the current preview, putting a CollectionViewSource in XAML doesn’t appear to work when printing. The grouped data does not appear to be available for binding at the point at which printing happens. (The data binding system often defers loading of data. This is usually not a problem on screen, because the screen will update as soon as the information becomes available. But with printing it can be a problem: if you don’t have the data by the time you print, it won’t make it onto the page.) By putting this in code and extracting the view source’s View property directly, we seem to get the grouped data when needed.
  4. Run the application.
  5. Log in as the user ian and password of P@ssw0rd
  6. Go to the event planner for the first event.
  7. Click the Print… button. You should see the print dialog appear.
  8. Select a printer.
    Note:
    You might want to use the Microsoft XPS Document Writer because that way, if things aren’t perfect first time, you won’t waste paper printing things out for real.
  9. Print the document, and if you printed to an XPS file, find it and open it. You should see the schedule printed out.

    Figure 1

    Printing the Content