The combination of SinglePageViewer and FlowDocument allow developers to easily reflow their content into multiple columns given different window sizes or user zoom levels.

Whatever that means.

My turgid prose doesn’t really do it justice, so here’s a movie (1 MB, WMV format) that illustrates reflow and zoom in action:

Preview of a video showing reflow in Avalon/WPF

That’s the same demo I showed at my PDC talk, which I’ll continue to build out in future entries.

In last episode, we constructed a minimal ControlTemplate for SinglePageViewer. Although it did not expose any UI, it was fully functional in terms of keyboard shortcuts, selection, and layout. Now we’ll create a ControlTemplate with real UI. Here’s the markup (it’s long, but I’ll break out the important parts):

<SinglePageViewer xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
                  xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
                  MinZoom="50" MaxZoom="200" FontFamily="Candara, Verdana">

  <SinglePageViewer.Template>
    <ControlTemplate TargetType="{x:Type SinglePageViewer}">
      <AdornerDecorator>
        <Grid>
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="*" />

          <Border Grid.ColumnSpan="3" Margin="5,5,5,10" Padding="5"
                  BorderBrush="#aaa" BorderThickness="1,1,1,2"
                  Background="#fff">

            <Border.BitmapEffect>
              <DropShadowBitmapEffect />
            </Border.BitmapEffect>

            <DocumentPageView DocumentViewerBase.IsMasterPage="True"
                              PageNumber="0" />
          </Border>

          <TextBlock Grid.ColumnSpan="3" Margin="0,0,0,20"
                     VerticalAlignment="Bottom" HorizontalAlignment="Center"
                     Text="{Binding Path=MasterPageNumber, RelativeSource=/TemplatedParent}" />

          <Slider Grid.Column="1" Grid.Row="1" Margin="5,5,10,5"
                  Value="{Binding Path=Zoom, RelativeSource=/TemplatedParent}"
                  Minimum="{Binding Path=MinZoom, RelativeSource=/TemplatedParent}"
                  Maximum="{Binding Path=MaxZoom, RelativeSource=/TemplatedParent}"
                  TickFrequency="{Binding Path=ZoomIncrement, RelativeSource=/TemplatedParent}" />

          <Button Grid.Column="0" Grid.Row="1"
                  Command="PreviousPage"
                  IsEnabled="{Binding Path=CanGoToPreviousPage, RelativeSource=/TemplatedParent}">
              Previous Page
          </Button>

          <Button Grid.Row="1" Grid.Column="2"
                  Command="NextPage"
                  IsEnabled="{Binding Path=CanGoToNextPage, RelativeSource=/TemplatedParent}">
              Next Page
          </Button>
        </Grid>
      </AdornerDecorator>
    </ControlTemplate>
  </SinglePageViewer.Template>

  <FlowDocument>
    <Paragraph FontSize="16">
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      Curabitur sodales, sem nec scelerisque aliquam, ipsum nibh
      tristique nisl, eu dictum ipsum neque nec dolor. Nullam
      magna lacus, mollis faucibus, auctor eget, placerat sit
      amet, odio. Morbi feugiat, ligula tristique condimentum
      luctus, eros nisi vestibulum ante, a porta dolor nibh sed
      nisl. Cras a libero vitae leo eleifend tristique. Fusce
      consequat, urna in consectetuer aliquet, felis purus luctus
      nisl, at sollicitudin sem augue quis ante. Fusce convallis
      nibh quis nisl. Fusce dictum faucibus velit. Proin tempus
      orci a leo. Integer suscipit. Vestibulum nisl libero, congue
      at, mollis sed, fringilla nec, lorem. Vestibulum ante ipsum
      primis in faucibus orci luctus et ultrices posuere cubilia
      Curae; Donec ac leo ut lectus tempus vestibulum. Proin vel
      enim eget nisi rhoncus mollis. Duis dolor mauris, dictum
      adipiscing, posuere nec, blandit et, orci.
    </Paragraph>
  </FlowDocument>
</SinglePageViewer>

Here are a couple of screenshots of the markup, the first is zoomed out to a small font size, the second has a large font size:

A screenshot of the SinglePageViewer control with a custom ControlTemplate

A screenshot of the SinglePageViewer control with a custom ControlTemplate

We’ve changed the structure of our ControlTemplate a bit, and added some new elements. Here’s a summary:

  • Border: In order ot make the content look like it’s on a physical page, we’ve wrapped the DocumentPageView with a Border element. Border is used to create a thin gray border and drop shadow (using DropShadowBitmapEffect) around the content.
  • TextBlock: This element displays the current page number. This is achieved by binding the Text property SinglePageViewer’s MasterPageNumber property.
  • Slider: The horizontal slider is bound to the Zoom property on SinglePageViewer.
  • Button: We’ve created two buttons, for “Previous Page” and “Next Page.” The Command property is set on the button, which hooks up the Button to fire the appropriate command when clicked. We’ve also bound the IsEnabled property to disable the buttons when appropriate (e.g. the “Previous Page” button is disabled if we’re displaying the first page).

Now we have a functional interface, but it’s not particularly pleasing to the eye. In part 3, we’ll create a more attractive look and feel.

In this post, we’ll take a deeper look into SinglePageViewer.

Like other WPF / Avalon Controls, you can modify the ControlTemplate in order to achieve sophisticated customization of your UI. Robbie has various examples where he’s customized the look for common UI controls — we’ll be using the same techniques for SinglePageViewer.

Let’s start with the minimal ControlTemplate for SinglePageViewer. I won’t bother to show the screenshot here, because it’s not very interesting — we’ve removed all of the UI, if you view this XAML all you’ll see is the “Lorem Ipsum” text. Here’s the markup:

<SinglePageViewer xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
                  xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">

  <SinglePageViewer.Template>
    <ControlTemplate TargetType="{x:Type SinglePageViewer}">
      <AdornerDecorator>
        <DocumentPageView SinglePageViewer.IsMasterPage="True" />
      </AdornerDecorator>
    </ControlTemplate>
  </SinglePageViewer.Template>

  <FlowDocument>
    <Paragraph>Lorem ipsum</Paragraph>
  </FlowDocument>
</SinglePageViewer>

This isn’t as self-explanatory as my previous samples, so I’ll go into more detail here. Here’s the breakdown tag-by-tag:

  • <SinglePageViewer.Template>: This is the property element syntax for setting the Template property on the SinglePageViewer. (This is the same technique you’d use in order to customize other controls, such as Button)
  • <ControlTemplate TargetType="{x:Type SinglePageViewer}">: This line instantiates the ControlTemplate we’re creating, and indicates that the template is targeting a SinglePageViewer through one of the built-in markup extensions. Much like the previous line, this is common across other controls.
  • <AdornerDecorator>: This element is required if you want to enable selection and the built-in keyboard shortcuts (selection is drawn in the adorner layer). It’s actually optional, but I recommend always using it so your application’s keyboard shortcuts are consistent with other applications.
  • <DocumentPageView SinglePageViewer.IsMasterPage="True" PageNumber="0" />: The DocumentPageView object is used by the SinglePageViewer to display a page of the document. If you wanted to create a border around the page, you’d do so by surrounding the DocumentPageView with a Border element.The two properties declared are used in order to disambiguate in the cases where we’re displaying more than one page. Although you can display more than one page in SinglePageViewer, as the name of the class indicates, it’s not really optimized for that scenario. However, it derives from the very general (and very flexible) DocumentViewerBase which does provide generic support for displaying multiple pages.Since we’re only displaying one page at a time, we always use the same property settings. Feel free to skip over the rest of this paragraph if you don’t care about the details. The IsMasterPage attached property tells the SinglePageViewer that the page being displayed by this DocumentPageView is the primary (aka “Master”) page — it is the one that controls the sizing of each page and the one used when navigating to a portion of the document (i.e. if you click a link to the third page, this determines which DocumentPageView is used to show that third page). Finally, the PageNumber property indicates the offset applied from SinglePageViewer’s MasterPageNumber property (if you had two pages, you would want one to have an offset of 0, and the other 1 — so you’re not displaying the same page twice).

Don’t worry if you still don’t understand 100% of the markup — you’ll typically copy it from an example (or use a tool like Sparkle to create it).

So now you’ve seen the a minimal ControlTemplate for SinglePageViewer. In the next installment, we’ll see how to add an actual interface for this element in order to make it usable.

SinglePageViewer is a new control for viewing flowing text that was introduced at PDC — astute observes may have noticed it during the keynotes (both Tuesday and Wednesday).

Here’s a screenshot and markup sample to get us started:

Screenshot of the SinglePageViewer control, showing some flowing text

<SinglePageViewer xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
                  ZoomIncrement="20" MinZoom="80" MaxZoom="180">
  <FlowDocument>
    <Paragraph FontFamily="Candara" FontSize="16">
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      Curabitur sodales, sem nec scelerisque aliquam, ipsum nibh
      tristique nisl, eu dictum ipsum neque nec dolor. Nullam
      magna lacus, mollis faucibus, auctor eget, placerat sit
      amet, odio. Morbi feugiat, ligula tristique condimentum
      luctus, eros nisi vestibulum ante, a porta dolor nibh sed
      nisl. Cras a libero vitae leo eleifend tristique. Fusce
      consequat, urna in consectetuer aliquet, felis purus luctus
      nisl, at sollicitudin sem augue quis ante. Fusce convallis
      nibh quis nisl. Fusce dictum faucibus velit. Proin tempus
      orci a leo. Integer suscipit. Vestibulum nisl libero, congue
      at, mollis sed, fringilla nec, lorem. Vestibulum ante ipsum
      primis in faucibus orci luctus et ultrices posuere cubilia
      Curae; Donec ac leo ut lectus tempus vestibulum. Proin vel
      enim eget nisi rhoncus mollis. Duis dolor mauris, dictum
      adipiscing, posuere nec, blandit et, orci.
    </Paragraph>
  </FlowDocument>
</SinglePageViewer>

The UI is a little ugly, and fortunately temporary — you’ll see the new, more graceful version in the next pre-release.

SinglePageViewer does a good job of highlighting Avalon/WPF’s ability to dynamically scale and reflow content by bringing the zoom functionality to the forefront. You can use the Zoom, MinZoom, MaxZoom, and ZoomIncrement propeties to tweak the zoom behavior for the viewer.

In the next post, we’ll learn how to customize the UI to create a branded look for the document.

The number of WPF/Avalon bloggers continues to grow. I have the privilege of being the first to out Beatriz Costa, who is an awesome tester on our Data team.

Tim’s list is slightly out of date, we have a number of new team members blogging that aren’t on the list yet (hopefully I haven’t missed any):

Update 9/29: Tim has updated his list, and promises to keep it up to date.

A portion of the Windows Presentation Foundation / Avalon team at the PDC (too many to list), Los Angeles, CA

A subset of the Avalon Windows Presentation Foundation team at the PDC (full size)

Don’t forget to check out my talk tommorrow morning at 8:30 in room 402AB.

Here’s a sneak peak at one of the demos I’ll show you how to build during the hour:

An attractive multi-column document created using Avalon

The past several weeks have been quite hectic as I prepare for the PDC while still trying to maintain my normal job.

For those attending, I’ve committed to the following events:

Thursday, 9/15 9:30am - 12:30pm Hands-on Labs Check out the labs, where you can play with the latest bits and get answers from the people who built the product.
12:30pm-3:30pm Track Lounge Along with many other team members, I’ll be hanging out at the Track Lounge answering any questions or, possibly, playing Jenga.
6:30pm-9:30pm Ask the Experts (Avalon Readability) Hanging out with a bit more structure, I guess. No Jenga.
Friday, 9/16 8:30am-9:45am My talk Come check out my talk, it’s much better than staying in your hotel room, hung over.
11:30am-2:30pm Track Lounge Hanging out after my talk

Those are only the times I’ve scheduled for myself. Generally, I’ll probably be in the Track Lounge when I’m not supporting my teammates at their talks.

Tip All the Avalon Windows Presentation Foundation people (there are around 60) will be wearing bright orange shirts. Except me, I’ll probably have a sports jacket on.

Those of you attending the PDC can check out my talk: PRS330 - Windows Presentation Foundation (”Avalon”): Creating Rich Content Experiences in Your Applications. Here’s the official abstract:

Using the Windows Presentation Foundation (formerly codename “Avalon”), you can provide your users with world-class content and document experiences within your application. Learn how to take your existing XML content and take advantage of services such as “Metro”, pagination, columns, and sub-pixel ClearType rendering to create a rich and branded display within your application.

In my last post, I mentioned how the Stretch.UniformToFill value can be a useful tool when creating a layout. In this post, we’ll use the functionality to create this flyer for the Tea Kitchen, a cozy little spot where all my imaginary friends hang out.

Flyer for the Tea Kitchen

Raw Materials

The original tea cup image comes from Michael Connors, found via the morgueFile — I’ve mirrored the file locally on this server. The font is Bickham Script Pro.

Markup

Here’s the markup that produced the screenshot above:

<Grid xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Margin="10">
  <Grid MinHeight="150" MaxHeight="640" MinWidth="250" MaxWidth="480">
    <Image VerticalAlignment="Center" Stretch="UniformToFill" Source="http://fortes.com/2005/07/15/teakitchen/teacup.jpg" />
    <Viewbox>
      <TextFlow Height="220" Foreground="VerticalGradient #ffff #afff"
                Width="400" FontSize="80" VerticalAlignment="Center" FontFamily="Bickham Script Pro, Lucida Handwriting" LineHeight="90">
        <Paragraph>
          <Bold Typography.StylisticAlternates="1">T</Bold>e<Bold Typography.StylisticAlternates="1">a</Bold>
        </Paragraph>
        <Paragraph>
          <Bold Typography.StylisticAlternates="1">K</Bold>itche<Bold Typography.StylisticAlternates="5">n</Bold>
        </Paragraph>
      </TextFlow>
    </Viewbox>
  </Grid>
</Grid>

Notice that the Image has UniformToFill set for it’s Stretch property — this ensures that the background image won’t leave any empty space within the layout. The center of the teacup image is the important portion, so we’ve set VerticalAlignment to Center in order to keep the center of the image in view at all times, cropping the top and bottom portions of the picture when window isn’t tall enough to fit the entire image.

The text is contained within a Viewbox in order to scale up or down within the flyer, depending on the window size. I’ve used Stylistic Alternates in order to show the capital letters with big swashes.

Resize Behavior

When the window width is reduced, notice how we’re cropping the right-side of the image:

Flyer for the Tea Kitchen

When we reduce the height, we’re cropping the top and bottom poritions of the image, keeping the center visible:

Flyer for the Tea Kitchen

By using UniformToFill, we can ensure that the image in the background will both fill our window size while keeping the image at it’s original aspect ratio.

I’ve also set the MinWidth, MaxWidth, MinHeight, and MaxHeight properties on the parent Grid — outside of those size ranges, the flyer starts looking bad (the text is too small, or the image starts getting pixelated). I’ll talk about how we can deal with this issue in a future post.