Skip past navigation, straight to the content

Monthly Archives: October 2005

M.I.A. at the Commodore Ballroom

M.I.A. plays at the Commodore Ballroom, Vancouver, BC

M.I.A. seems more confident than the last time I saw her, but her set is still way short, clocking in at 45 minutes (!). Also, she seems to have replaced Diplo another DJ, who kinda resembles Diplo from far away. Beats (and video) were the same as last show — disappointing.

Solid (B) High energy crowd, short set

Aside: This was the first show I’ve seen at the Commodore, which is Vancouver’s version of the Showbox (although it seems larger). The venue was fine, up until the show ended — the place must be a fire risk, it took at least 10 minutes to get out of the building.

A crowd exits at the Commodore Ballroom, Vancouver, BC

Second aside: Due to lack of research, we didn’t know that the Arcade Fire played in Vancouver the same night. Oops.

Introducing SinglePageViewer

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.

Customizing SinglePageViewer (Part 1)

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.

Customizing SinglePageViewer (Part 2)

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.

Reflow in Action

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.

Recent Albums

It’s been a while since I’ve written one of my insightful album reviews. Here’s a run down of a few albums from the last few months:

Packwood Lake

Watch the PDC Talks

As Mike announced last night, the PDC talks have been posted online for streaming. Here’s a direct link to my talk: PRS330: Creating Rich Content Experiences in Your WPF/Avalon Applications.

Every talk is posted, so there’s a lot to watch. Here are direct links to talks by my fellow team members (note that there are other WPF/Avalon-related sessions as well, make sure to browse the Presentation track if you’re interested):

Speaker(s) Session
Michael Wallent PRS200: Choosing the Right Presentation Technology: WPF/Avalon, Windows Forms, ASP.NET, IE, and more
Rob Relyea PRS305: A Lap around WPF/Avalon
Pablo Fernicola PRS309: Overview of Windows Vista Graphics
Nick Kramer PRS313: Integrating WPF/Avalon with your Win32/MFC Application
Lauren Lavoie PRS314: Using Application Services
Robbie Ingebretsen PRS317: Beautiful Code, Beautiful Design - Applications Your Designers Can Work With
Namita Gupta PRS324: Using Data in Your WPF/Avalon Applications
Kam VedBrat PRS325: Advanced Graphics (Part 1): 2D, 3D, and Text
Alexander Stevenson PRS327: Optimizing WPF/Avalon applications for performance
Greg Schechter PRS328: Advanced Graphics (Part 2): Animations, Imaging, Effects, and Media
Henry Hahn PRS329: Building User Interfaces with Advanced Layout Techniques
Filipe Fortes PRS330: Creating Rich Content Experiences in Your WPF/Avalon Applications
Kevin Moore PRS431: Building Custom Controls
Chris Anderson, Jeff Bogdan, and Greg Schechter PRS435: Going under the Hood to Understand the Architecture

Note: You’ll need to use Internet Explorer to view the presentations.

Ed Interlock

Ed Interlock is an OpenType font with some cool ligatures. Here’s a screenshot with a sample phrase:

The words 'Do you have a font fetish?' written in the Ed Interlock font -- with ligatures enabled

Here’s the same text with the ligatures disabled:

The words 'Do you have a font fetish?' written in the Ed Interlock font -- with ligatures disabled

In case you have the font installed, here’s the markup I used to generate the screenshots:

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

  <TextFlow FontFamily="Ed Interlock" TextAlignment="Center" Margin="10" FontSize="40">
    <!-- Ligatures default to true, but set property here for completeness  -->
    <Paragraph Typography.StandardLigatures="True">DO YOU HAVE A FONT FETISH?</Paragraph>
  </TextFlow>
  <TextFlow FontFamily="Ed Interlock" TextAlignment="Center" Margin="10" FontSize="40">
    <Paragraph Typography.StandardLigatures="False">DO YOU HAVE A FONT FETISH?</Paragraph>
  </TextFlow>

</StackPanel>

Number Substitution

One of the random things I’ve learned about while working on WPF/Avalon are the different number systems used through out the world. I wrote a XAML example that shows some the support for number substitution. Here’s a screenshot and the markup:

The number 4.39 in English, Portuguese, Arabic, Thai, and Panjabi writing systems

<Border xmlns="http://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
        Padding="10" HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="#ccc" BorderThickness="1">

  <Grid HorizontalAlignment="Center">
    <Grid.Resources>
      <!-- Common properties for all TextBlock elements -->
      <Style TargetType="{x:Type TextBlock}">
        <Setter Property="FontFamily" Value="Constantia" />
        <Setter Property="FontSize" Value="20" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Margin" Value="5" />
      </Style>
      <!-- Properties for the number display, the databinding is done here -->
      <Style x:Key="NumberDisplay" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
        <Setter Property="NumberSubstitution.Substitution" Value="Traditional" />
        <Setter Property="Text" Value="{Binding Path=Value, ElementName=NumberSlider}" />
        <Setter Property="Grid.Column" Value="1" />
        <Setter Property="MinWidth" Value="50" />
      </Style>
    </Grid.Resources>

    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <!-- Title and Slider -->
    <StackPanel Grid.ColumnSpan="2">
      <TextBlock Grid.ColumnSpan="2" FontWeight="Bold" FontFamily="Candara" FontSize="30">Number Substitution</TextBlock>
      <Slider Grid.ColumnSpan="2" Minimum="0" Maximum="10" Name="NumberSlider" IsSnapToTickEnabled="True" TickFrequency=".01" Width="Auto" MaxWidth="300" Margin="10" />
    </StackPanel>

    <!-- Row Shading -->
    <Rectangle Grid.Row="1" Grid.ColumnSpan="2" Fill="#dec" />
    <Rectangle Grid.Row="2" Grid.ColumnSpan="2" Fill="#ffd" />
    <Rectangle Grid.Row="3" Grid.ColumnSpan="2" Fill="#dec" />
    <Rectangle Grid.Row="4" Grid.ColumnSpan="2" Fill="#ffd" />
    <Rectangle Grid.Row="5" Grid.ColumnSpan="2" Fill="#dec" />

    <!-- Labels -->
    <TextBlock Grid.Row="1">English (en-us)</TextBlock>
    <TextBlock Grid.Row="2">Portuguese (pt-pt)</TextBlock>
    <TextBlock Grid.Row="3">Arabic (ar-sa)</TextBlock>
    <TextBlock Grid.Row="4">Thai (th-th)</TextBlock>
    <TextBlock Grid.Row="5">Panjabi (pa-in)</TextBlock>

    <!-- Numbers -->
    <TextBlock Grid.Row="1" xml:lang="en-us" Style="{StaticResource NumberDisplay}" />
    <TextBlock Grid.Row="2" xml:lang="pt-pt" Style="{StaticResource NumberDisplay}" />
    <TextBlock Grid.Row="3" xml:lang="ar-sa" Style="{StaticResource NumberDisplay}" />
    <TextBlock Grid.Row="4" xml:lang="th-th" Style="{StaticResource NumberDisplay}" />
    <TextBlock Grid.Row="5" xml:lang="pa-in" Style="{StaticResource NumberDisplay}" />
  </Grid>
</Border>

You can copy and paste the code into XamlPad and manipulate the slider in order to see the changing values live (which is pretty neat in a very nerdy way).

The markup may look large, but that’s mostly my formatting (because I like to make things look pretty). Here’s a simplified excerpt to show the important part:

<TextBlock xml:lang="ar-sa" NumberSubstitution.Substitution="Traditional" />

The snippet above does two things. First it sets the culture of the content being displayed in the TextBlock, in this case Saudi Arabic (ar-sa). In order to activate the number substitution in this sample, we also have to set the NumberSubstitution.Substitution property to Traditional, which tells the system to look at the culture of the current context, instead of the system setting.

I’ll close this entry with a second screenshot, showing a different number this time:

The number 7.01 in English, Portuguese, Arabic, Thai, and Panjabi writing systems

Update 10/28: Norris Cheng, a fellow Avalon team member, pointed out something I should clarify/correct in this entry (which I’ve paraphrased below):

The NumberSubstitution.CultureSource tells the system which CultureInfo it should look at: xml:lang (the default), the user’s machine setting, or the NumberSubstitution.CultureOverride property. The reason we set NumberSubstitution.Substitution (which should really be called SubstitutionMethod is that even for cultures like ar-SA, latin digits are used by default — you need to set NumberSubstitution.Substitution to Traditional to tell the system to use the traditional digits instead.