Tải bản đầy đủ (.pdf) (102 trang)

programming windows phone 7 phần 8 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.52 MB, 102 trang )




























studentDisplay.Visibility = Visibility.Visible;
else if (touchPoint.Action == TouchAction.Up)
studentDisplay.Visibility = Visibility.Collapsed;
}


}
}
As you run your fingers across the bars, you can see the student that each bar represents:
A Card File Metaphor
With the previous GpiBarChart program, in a sense we’ve managed to fit all the students onto
a single screen, but the information is limited. Is there a way to get more information on the
screen? One popular metaphor for displaying data is the card file. Normally only part of each
card is visible but such a program also includes a facility for viewing an entire card.
In preparation for this job, I created a new panel in the Petzold.Phone.Silverlight library. In
some ways this panel is similar to the UniformStack panel that I described in Chapter 9. Like
UniformStack, this new panel gives all of its children an equal amount of space. But unlike
UniformStack, this new panel actually overlaps its children if necessary to fit them all in the
available space. For that reason, it’s called OverlapPanel.
OverlapPanel defines an Orientation property and arranges its children either horizontally or
vertically. If OverlapPanel is arranging its children horizontally, each child is positioned slightly
to the right of the child before it, leaving the left-most sliver of the previous child visible. For a
vertical orientation, the top of each child is visible.
If there are very many children, then that visible sliver will become very small. To make
OverlapPanel more useful, it should be possible to specify that the sliver be at least a
minimum height or width, even if that causes the contents of the panel to overrun the
available space. In possibly going beyond the space available for it, the OverlapPanel behaves
much like a regular StackPanel. A ScrollViewer will be necessary to view all the items.
699









































OverlapPanel defines two properties, Orientation and MinimumOverlap:
Silverlight Project: Petzold.Phone.Silverlight File: OverlapPanel.cs (excerpt)
public class OverlapPanel : Panel
{
Size maxChildSize = new Size();
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation",
typeof(Orientation),
typeof(OverlapPanel),
new PropertyMetadata(Orientation.Horizontal, OnAffectsMeasure));
public static readonly DependencyProperty MinimumOverlapProperty =
DependencyProperty.Register("MinimumOverlap",
typeof(double),
typeof(OverlapPanel),
new PropertyMetadata(0.0, OnAffectsMeasure));
public Orientation Orientation
{
set { SetValue(OrientationProperty, value); }
get { return (Orientation)GetValue(OrientationProperty); }
}
public double MinimumOverlap
{
set { SetValue(MinimumOverlapProperty, value); }
get { return (double)GetValue(MinimumOverlapProperty); }
}
static void OnAffectsMeasure(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{

(obj as OverlapPanel).InvalidateMeasure();
}

}
Changes to either of these two properties causes a call to InvalidateMeasure, which initiates a
new layout pass.
The MeasureOverride method first enumerates through all its children to obtain the maximum
child size. Of course, when you use OverlapPanel with an ItemsControl or ListBox, all the
children will probably have the same size.
700






















































Silverlight Project: Petzold.Phone.Silverlight File: OverlapPanel.cs (excerpt)
protected override Size MeasureOverride(Size availableSize)
{
if (Children.Count == 0)
return new Size(0, 0);
maxChildSize = new Size();
foreach (UIElement child in Children)
{
if (Orientation == Orientation.Horizontal)
child.Measure(new Size(Double.PositiveInfinity, availableSize.Height));
else
child.Measure(new Size(availableSize.Width, Double.PositiveInfinity));
maxChildSize.Width = Math.Max(maxChildSize.Width,
child.DesiredSize.Width);
maxChildSize.Height = Math.Max(maxChildSize.Height,
child.DesiredSize.Height);
}
if (Orientation == Orientation.Horizontal)
{
double maxTotalWidth = maxChildSize.Width * Children.Count;
double minTotalWidth = maxChildSize.Width +
MinimumOverlap * (Children.Count - 1);
if (Double.IsPositiveInfinity(availableSize.Width))
return new Size(minTotalWidth, maxChildSize.Height);
if (maxTotalWidth < availableSize.Width)
return new Size(maxTotalWidth, maxChildSize.Height);
else if (minTotalWidth < availableSize.Width)
return new Size(availableSize.Width, maxChildSize.Height);

return new Size(minTotalWidth, maxChildSize.Height);
}
// Orientation = Vertical
double maxTotalHeight = maxChildSize.Height * Children.Count;
double minTotalHeight = maxChildSize.Height +
MinimumOverlap * (Children.Count - 1);
if (Double.IsPositiveInfinity(availableSize.Height))
return new Size(maxChildSize.Width, minTotalHeight);
if (maxTotalHeight < availableSize.Height)
return new Size(maxChildSize.Width, maxTotalHeight);
else if (minTotalHeight < availableSize.Height)
701











































return new Size(maxChildSize.Width, availableSize.Height);
return new Size(maxChildSize.Width, minTotalHeight);
}
The method then splits into two different sections depending on the Orientation property. For
example, for the vertical orientation (which I’ll be using in the example below), the method
calculates a maxTotalHeight, when all the children are side-by-side without overlap, and a

minTotalHeight, when the children are overlapped to the maximum extent. If the available
height is not infinite (a possibility handled separately), then the available height is either
greater than maxTotalHeight or between minTotalHeight and maxTotalHeight, or less than
minTotalHeight. If all the children can fit side-by-side in the available space, then that’s the
space requested. But the method never requests less height than it needs to display all the
children.
The ArrangeOverride method is somewhat simpler. The increment value is the width or height
of the sliver of each child that will always be visible:
Silverlight Project: Petzold.Phone.Silverlight File: OverlapPanel.cs (excerpt)
protected override Size ArrangeOverride(Size finalSize)
{
if (Children.Count == 0)
return finalSize;
double increment = 0;
if (Orientation == Orientation.Horizontal)
increment = Math.Max(MinimumOverlap,
(finalSize.Width - maxChildSize.Width) / (Children.Count - 1));
else
increment = Math.Max(MinimumOverlap,
(finalSize.Height - maxChildSize.Height) / (Children.Count - 1));
Point ptChild = new Point();
foreach (UIElement child in Children)
{
child.Arrange(new Rect(ptChild, maxChildSize));
if (Orientation == Orientation.Horizontal)
ptChild.X += increment;
else
ptChild.Y += increment;
}
return finalSize;

}
702






































The StudentCardFile project has references to the Petzold.Phone.Silverlight and
ElPasoHighSchool libraries. The MainPage.xaml file includes the StudentBodyPresenter in the
Resources collection:
Silverlight Project: StudentCardFile File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources>
<elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" />
</phone:PhoneApplicationPage.Resources>
The content area is rather simple, containing only a ScrollViewer and an ItemsControl. The
ItemsPanel property of the ItemsControl references the OverlapPanel with two properties set:
Silverlight Project: StudentCardFile File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
DataContext="{Binding Source={StaticResource studentBodyPresenter},
Path=StudentBody}">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StudentCard />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>

<ItemsPanelTemplate>
<petzold:OverlapPanel Orientation="Vertical"
MinimumOverlap="24" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
The simplicity of the markup here is mostly a result of the DataTemplate property of the
ItemsControl being set to another control named StudentCard.
StudentCard derives from UserControl. Deriving from UserControl is a common technique for
creating a control to serve as a DataTemplate. If you ignore the ellipses (…) below, this is a
very straightforward assemblage of a TextBlock and Image elements, with a collapsed
Rectangle used as a dividing line:
703


















































Silverlight Project: StudentCardFile File: StudentCard.xaml (excerpt)
<UserControl x:Class="StudentCardFile.StudentCard"
xmlns=" /> xmlns:x=" /> FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Width="240" Height="240">

<Border BorderBrush="{StaticResource PhoneAccentBrush}"
BorderThickness="1"
Background="{StaticResource PhoneChromeBrush}"
CornerRadius="12"
Padding="6 0">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding FullName}" />
<Rectangle Grid.Row="1"
Fill="{StaticResource PhoneAccentBrush}"
Height="1"
Margin="0 0 0 4" />
<Image Grid.Row="2"

Source="{Binding PhotoFilename}" />
<StackPanel Grid.Row="3"
Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock Text="GPA = " />
<TextBlock Text="{Binding GradePointAverage}" />
</StackPanel>
</Grid>
</Border>
</UserControl>
704






The cards are listed down the left side of the display but only the top of each card is visible.
Conveniently, the top of each card is a TextBlock displaying the student’s name:
I set MinimumOverlap to a value sufficient to display this TextBlock. As you scroll down to the
bottom, you’ll see that the bottom card is entirely visible:
705





















That’s great if you want to look at the very last card, but rather deficient otherwise. What we
need is a way to selectively bring a particular card into view. One approach might be to
change the Canvas.ZIndex attached property of a particular card. Or, the whole deck of cards
might be re-ordered to move a particular card to the topmost position.
I decided I wanted a selected card to slide out of the deck when it’s touched, and then slide
back when the card is touched again, or when another card is touched.
As you start integrating other code with ScrollViewer, you’ll discover that ScrollViewer tends to
hog the Manipulation events. Obviously ScrollViewer needs these Manipulation events for its
own scrolling logic. But that makes it difficult for visual descendents of the ScrollViewer (such
as these StudentCard elements) to process Manipulation events of their own for sliding in and
out of the deck
For that reason, I decided that StudentCard would install a handler for the low-level
Touch.FrameReported event, and to use that to toggle a dependency property named IsOpen.
Here’s that property in the StudentCard code-behind file:
Silverlight Project: StudentCardFile File: StudentCard.xaml.cs (excerpt)
public partial class StudentCard : UserControl
{


public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen",
706




































typeof(bool),
typeof(StudentCard),
new PropertyMetadata(false, OnIsOpenChanged));

bool IsOpen
{
set { SetValue(IsOpenProperty, value); }
get { return (bool)GetValue(IsOpenProperty); }
}

}
I’ll show you the property-changed handler for IsOpen shortly.
When you touch one instance of StudentCard, it is supposed to slide out of the deck, but if
another card is currently exposed, that card should slide back into the deck. If the CardFile
class is to handle this logic on its own, each instance of CardFile needs access to all the other
instances. For that reason, I defined a static field of type List to maintain these instances:
Silverlight Project: StudentCardFile File: StudentCard.xaml.cs (excerpt)
public partial class StudentCard : UserControl
{
static List<StudentCard> studentCards = new List<StudentCard>();

public StudentCard()

{
InitializeComponent();
studentCards.Add(this);
}

}
Each new instance simply adds itself to the collection.
It also became apparent to me that each individual StudentCard instance does not need its
own handler for the Touch.FrameReported event. All instances could share the same static
handler installed in the static constructor and referencing static fields:
Silverlight Project: StudentCardFile File: StudentCard.xaml.cs (excerpt)
public partial class StudentCard : UserControl
{

static int contactTime;
static Point contactPoint;

static StudentCard()
{
707

















































Touch.FrameReported += OnTouchFrameReported;
}

static void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint touchPoint = args.GetPrimaryTouchPoint(null);
if (touchPoint != null && touchPoint.Action == TouchAction.Down)
{
contactPoint = touchPoint.Position;
contactTime = args.Timestamp;
}
else if (touchPoint != null && touchPoint.Action == TouchAction.Up)
{
// Check if finger is directly over StudentCard or child
DependencyObject element = touchPoint.TouchDevice.DirectlyOver;
while (element != null && !(element is StudentCard))
element = VisualTreeHelper.GetParent(element);
if (element == null)
return;
// Get lift point and calculate difference
Point liftPoint = touchPoint.Position;
double distance = Math.Sqrt(Math.Pow(contactPoint.X - liftPoint.X, 2) +
Math.Pow(contactPoint.Y - liftPoint.Y, 2));

// Qualify as a Tap if distance < 12 pixels within 1/4th second
if (distance < 12 && args.Timestamp - contactTime < 250)
{
// Enumerate StudentCard objects and set IsOpen property
foreach (StudentCard studentCard in studentCards)
studentCard.IsOpen =
(element == studentCard && !studentCard.IsOpen);
}
}
}

}
With a little experimentation, I determined that I wanted a tap to qualify as a touch and
release with ¼ second where the touch point moves less than 12 pixels. That seemed to be
about right and still allow flicks to be recognized by the ScrollViewer.
At the bottom of this method a foreach loop enumerates through all the StudentCard objects
and sets the IsOpen property on each one. IsOpen is always set to false if the StudentCard is
not the touched element, and IsOpen is also set to false if IsOpen is currently true. Otherwise,
if the StudentCard object is the touched element, and IsOpen is currently false, then it’s set to
true. Of course, as a dependency property, IsOpen property-changed handlers will only be
called if the property is truly changing.
708









































I have not yet shown you the property-changed handler for the IsOpen property. As usual, the
static version calls the instance version:
Silverlight Project: StudentCardFile File: StudentCard.xaml.cs (excerpt)
public partial class StudentCard : UserControl
{

static void OnIsOpenChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
(obj as StudentCard).OnIsOpenChanged(args);
}

void OnIsOpenChanged(DependencyPropertyChangedEventArgs args)
{
VisualStateManager.GoToState(this, IsOpen ? "Open" : "Normal", false);
}
}
The instance version calls VisualStateManager.GoToState. Although the Visual State Manger is
most frequently used in connection with controls and controls template, you can also use it
with UserControl derivatives such as StudentCard. Calling GoToState is how you trigger a state
change from code.
In the XAML file, the Visual State Manager markup must appear right after the topmost
element in the visual tree. In the case of StudentCard.xaml, that’s the Border element. Here’s
the rest of StudentCard.xaml (with some repetition from the previous excerpt) showing the
Visual State Manager markup targeting a TranslateTransform set on the control itself:
Silverlight Project: StudentCardFile File: StudentCard.xaml (excerpt)
<UserControl x:Class="StudentCardFile.StudentCard"
xmlns="
xmlns:x="

FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Width="240" Height="240">
<UserControl.RenderTransform>
<TranslateTransform x:Name="translate" />
</UserControl.RenderTransform>
<Border BorderBrush="{StaticResource PhoneAccentBrush}"
BorderThickness="1"
Background="{StaticResource PhoneChromeBrush}"
CornerRadius="12"
Padding="6 0">
709




























<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
To="220" Duration="0:0:1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
Duration="0:0:1" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

</Border>

</UserControl>
When you tap one of the items, it slides out to reveal the full card:
710







Throughout this chapter I’ve tried to do several different types of jobs entirely in XAML. That’s
not always possible; very often code is required, particularly for handling touch input.
But much of the required code doesn’t replace the XAML: the code helps support the markup.
These classes take the form of binding converters and custom panels that are referenced
within the XAML file. In general, you should try to code for XAML and not instead of XAML,
and you’ll be a happier and better Silverlight and Windows Phone 7 programmer.
711





























Chapter 18
Pivot and Panorama
Silverlight applications that need to present large amounts of information to the user have
traditionally used a page-oriented navigation structure. On the phone, however, a division of
your program into pages might not be the best approach. The phone’s portrait form factor,
the ease of multi-touch, and a recent emphasis on “fluid user interfaces” all suggest other
types of layout. Two such alternatives are available in Windows Phone 7 in new controls
named Pivot and Panorama.
Both Pivot and Panorama are in the Microsoft.Phone.Controls library and any program that
uses these controls will need a reference to that DLL. The controls are defined in the
Microsoft.Phone.Controls namespace with subsidiary components in
Microsoft.Phone.Controls.Primitives, but it’s unlikely you’ll need those other classes unless
you’re customizing the controls.
Conceptually, Pivot and Panorama are very similar. Both controls provide a way to organize
discrete components of your application horizontally in a virtual space that can be several

times wider than the actual width of the phone. You move horizontally through the control
simply by sweeping your finger across the screen. Although the Pivot and Panorama controls
seem to be designed primarily for portrait mode, they can be used in landscape mode as well.
Compare and Contrast
Both Pivot and Panorama derive from ItemsControl by way of a class with a generic
parameter:
public class TemplatedItemsControl<T> : ItemsControl where T : new(), FrameworkElement
This indicates an ItemsControl that is intended to be filled with objects of type T. Both Pivot
and Panorama derive from TemplatedItemsControl with a type parameter set to PivotItem or
PanoramaItem, respectively:
public class Pivot : TemplatedItemsControl<PivotItem>
public class Panorama : TemplatedItemsControl<PanoramaItem>
The Pivot control expects to contain items of type PivotItem while the Panorama control
expects to contain items of type PanoramaItem. Both PivotItem and PanoramaItem derive
from ContentControl. If you’re filling the Items collection of a Pivot or Panorama object
explicitly in XAML and code, you’ll want to fill it with PivotItem or PanoramaItem items,
because there’s a crucial Header property you need to set on these controls. If you instead use
a binding on the ItemsSource property defined by ItemsControl, these PivotItem and
712












































PanoramaItem objects are created for you behind the scenes, and you set the Header
property through a template. (Don’t worry: I’ll have examples.)
To instantiate these controls in a XAML file you’ll need an XML namespace declaration for the
Microsoft.Phone.Controls library and namespace:
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
Perhaps the best way to explore these classes is to experiment with an actual example. The
New Project dialog in Visual Studio allows you to create an project of type Windows Phone
Pivot Application or Windows Phone Panorama Application, and you can surely experiment
with those. For the demonstration programs in this chapter I took a different approach.
Here’s a MainPage.xaml file from a project named PivotDemonstration. I created this project
normally, that is, by selecting Windows Phone Application from the New Project dialog box.
But then I deleted most of the contents of MainPage.xaml except the PhoneApplicationPage
tags. I added the XML namespace declaration for “controls” (it’s the widest one) and I
replaced the contents of the page with a Pivot and four nested PivotItem children:
Silverlight Project: PivotDemonstration File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage
x:Class="PivotDemonstration.MainPage"
xmlns=" /> xmlns:x=" /> xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:controls="clr-
namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:d="
xmlns:mc="
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"

shell:SystemTray.IsVisible="True">
<controls:Pivot Title="PIVOT DEMONSTRATION">
<controls:PivotItem Header="ListBox">

</controls:PivotItem>
<controls:PivotItem Header="Ellipse">

</controls:PivotItem>
<controls:PivotItem Header="TextBlock">

</controls:PivotItem>
713














































<controls:PivotItem Header="Animation">

</controls:PivotItem>

</controls:Pivot>
</phone:PhoneApplicationPage>
The Pivot control’s Title property is set to “PIVOT DEMONSTRATION.” By default, this title will
appear in the same location and be the same size as the text displayed at the top of the
normal Windows Phone page. (That’s the text normally displayed by the TextBlock with the
name ApplicationTitle.) Each of the four PivotItem controls has a Header property set; this text
appears in the same location and is the same size as the customary TextBlock named
PageTitle.
The PivotItem control derives from ContentControl, so you can put pretty much anything in
those controls. I gave the first PivotItem a ListBox containing all the fonts available to
Windows Phone 7 programs, including a simple DataTemplate:
Silverlight Project: PivotDemonstration File: MainPage.xaml (excerpt)
<controls:PivotItem Header="ListBox">
<ListBox FontSize="{StaticResource PhoneFontSizeLarge}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
FontFamily="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<system:String>Arial</system:String>
<system:String>Arial Black</system:String>
<system:String>Calibri</system:String>
<system:String>Comic Sans MS</system:String>
<system:String>Courier New</system:String>
<system:String>Georgia</system:String>
<system:String>Lucida Sans Unicode</system:String>
<system:String>Portable User Interface</system:String>
<system:String>Segoe WP</system:String>
<system:String>Segoe WP Black</system:String>

<system:String>Segoe WP Bold</system:String>
<system:String>Segoe WP Light</system:String>
<system:String>Segoe WP Semibold</system:String>
<system:String>Segoe WP SemiLight</system:String>
<system:String>Tahoma</system:String>
<system:String>Times New Roman</system:String>
<system:String>Trebuchet MS</system:String>
<system:String>Verdana</system:String>
<system:String>Webdings</system:String>
</ListBox>
</controls:PivotItem>
714






















The PivotItem gives the ListBox an amount of space equal to the size of the page less the Title
text and the Header text:
The ListBox is vertically scrollable, of course. Notice the Header text of the second PivotItem in
a dimmed state next to the first one. That second PivotItem just displays an Ellipse:
Silverlight Project: PivotDemonstration File: MainPage.xaml (excerpt)
<controls:PivotItem Header="Ellipse">
<Ellipse>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Offset="0" Color="{StaticResource PhoneAccentColor}"
/>
<GradientStop Offset="0.5" Color="{StaticResource
PhoneBackgroundColor}" />
<GradientStop Offset="1" Color="{StaticResource
PhoneForegroundColor}" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</controls:PivotItem>
715




























This clearly shows exactly how large an area the PivotItem is offering to its content:
The third PivotItem contains a ScrollViewer with a large TextBlock containing the opening
paragraph from a well-known novel:
Silverlight Project: PivotDemonstration File: MainPage.xaml (excerpt)
<controls:PivotItem Header="TextBlock">
<ScrollViewer>
<! from >
<TextBlock TextWrapping="Wrap">
For a long time I used to go to bed early. Sometimes, when I had put out
my candle, my eyes would close so quickly that I had not even time to

say "I'm going to sleep." And half an hour later the thought that it was
time to go to sleep would awaken me; I would try to put away the book
which, I imagined, was still in my hands, and to blow out the light; I
had been thinking all the time, while I was asleep, of what I had just
been reading, but my thoughts had run into a channel of their own,
until I myself seemed actually to have become the subject of my book:
a church, a quartet, the rivalry between François I and Charles V. This
impression would persist for some moments after I was awake; it did not
disturb my mind, but it lay like scales upon my eyes and prevented them
from registering the fact that the candle was no longer burning. Then
it would begin to seem unintelligible, as the thoughts of a former
existence must be to a reincarnate spirit; the subject of my book would
separate itself from me, leaving me free to choose whether I would form
part of it or no; and at the same time my sight would return and I
would be astonished to find myself in a state of darkness, pleasant and
716



























restful enough for the eyes, and even more, perhaps, for my mind, to
which it appeared incomprehensible, without a cause, a matter dark
indeed.
</TextBlock>
</ScrollViewer>
</controls:PivotItem>
Once again, there’s no issue with scrolling:
The final PivotItem contains a TextBlock with several animations applied:
Silverlight Project: PivotDemonstration File: MainPage.xaml (excerpt)
<controls:PivotItem Header="Animation">
<TextBlock Text="Hello, Windows Phone 7!"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5 0.5">
<TextBlock.RenderTransform>
<CompositeTransform x:Name="xform" />
</TextBlock.RenderTransform>
</TextBlock>

<controls:PivotItem.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="xform"
717




























Storyboard.TargetProperty="Rotation"
From="0" To="360" Duration="0:0:3"
RepeatBehavior="Forever" />
<DoubleAnimation Storyboard.TargetName="xform"
Storyboard.TargetProperty="TranslateX"
From="0" To="300" Duration="0:0:5"
AutoReverse="True"
RepeatBehavior="Forever" />
<DoubleAnimation Storyboard.TargetName="xform"
Storyboard.TargetProperty="TranslateY"
From="0" To="600" Duration="0:0:7"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</controls:PivotItem.Triggers>
</controls:PivotItem>
The animations make the TextBlock move and spin around:
Notice the header of the first PivotItem to the right of the active one. The animations are
tailored for the approximate size of the content area of the PivotItem for the large screen in
portrait mode. If you turn the phone or emulator sideways, the TextBlock will drift off the
screen temporarily.
718


























The PanoramaDemonstration program is extremely similar to PivotDemonstration. For the
most part, every place in the MainPage.xaml file of PivotDemonstration where the word
“Pivot” occurs is replaced with the word “Panorama.” Beyond that, the only other difference
was the change in the Title property to lowercase:
Silverlight Project: PanoramaDemostration File: MainPage.xaml (excerpt)
<controls:Panorama Title="panorama demonstration">
<controls:PanoramaItem Header="ListBox">


</controls:PanoramaItem>
<controls:PanoramaItem Header="Ellipse">

</controls:PanoramaItem>
<controls:PanoramaItem Header="TextBlock">

</controls:PanoramaItem>
<controls:PanoramaItem Header="Animation">

</controls:PanoramaItem>
</controls:Panorama>
Although Pivot and Panorama are conceptually very similar, they have rather different
aesthetics. The next several screen shots show the two controls side-by-side with Pivot on the
left and Panorama on the right. Notice how the Title is handled in the Panorama: It’s much
larger and suggests that it stretches to encompass all the other items:
719











Although I haven’t done so here, generally you’ll set the Background property of the
Panorama control to an ImageBrush with a wide bitmap that spreads out behind the back.
(On the phone, look at the Games, Marketplace, and Pictures applications to get some ideas.)

As a result of the large Title, the Panorama offers less vertical space for the content of each
PanoramaItem. Slightly less horizontal space is available as well because the next item to the
right is peaking through at the right edge.
You can navigate forwards or backwards through the Pivot and Panorama just by sweeping
your finger to the right or left. With Panorama, sweeping your finger along the Title text feels
very natural. With the Pivot (but not the Panorama) you can navigate to one of the other
items by tapping its Header text:
720









Notice how the Title of the Panorama has also shifted to visually indicate where you are in
terms of the virtual width of all the content.
As you experiment with sweeping your finger across the screen, you’ll discover that the Pivot
and Panorama actually behave in very different ways: In both cases the Header texts are
somewhat visually uncoupled from the actual items. With the Pivot, one item moves
completely off the screen before the next item slides in; with the Panorama, you can see two
items simultaneously. Here’s a view in progress between two items:
721













The Panorama gives a much better sense of a wide virtual screen through which a viewport is
visible, particularly when used with a wide background bitmap. The Pivot seems more like it’s
occupying just the screen area and works by sliding individual items in and out of view.
The Pivot control defines several events that the Panorama control does not:
LoadingPivotItem, LoadedPivotItem, UnloadingPivotItem, UnloadedPivotItem. These events
signal when one item slips out of view and another item slips in. These events don’t quite
apply to the more fluid nature of the Panorama.
Both Pivot and Panorama define SelectionChanged events, as well as SelectedIndex and
SelectedItem. The selection is considered to be the PivotItem or PanoramaItem in full view,
and the event isn’t fired until the item finishes sliding fully into place.
722









Both Pivot and Panorama define TitleTemplate and HeaderTemplate properties of type
DataTemplate so if you use bindings to set the content of the control you can define a visual
tree to indicate how the Title property and Header properties use the data.

The HeaderTemplate property is particularly important if you bind the ItemsSource property of
Pivot or Panorama to a collection, in which case you aren’t creating the PivotItem or
PanoramaItem objects explicitly. You’ll need this HeaderTemplate for a binding to set the
Header text, but the template can consist solely of a TextBlock. You’ll see an example later in
this chapter.
723

×