The sample code for this section is located in the DeepZoomView
page and DeepZoomViewModel
class, in the downloadable sample code.
The sample allows the selection of a Deep Zoom image, via a ListPicker
control. The interface harnesses touch for zooming and panning of the MultiScaleImage
control (see Figure 7.11).
For information on the ListPicker
control, see Chapter 9, “Enriching the User Experience with the Windows Phone Toolkit Controls.” For information on touch, see Chapter 12, “Processing Touch Input.”
The view contains a grid with a ListPicker
, which is data bound to an ObservableCollection
of custom DeepZoomSource
objects. Each DeepZoomSource
holds the name and URL of a Deep Zoom image. When the user selects a different item from the ListPicker
, the SelectedItem
property of the ListPicker
is updated, which updates the DeepZoomSource
viewmodel property. See the following excerpt:
<Grid Grid.Row="1">
<toolkit:ListPicker ItemsSource="{Binding DeepZoomSources}"
SelectedItem="{Binding DeepZoomSource, Mode=TwoWay}">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"
Style="{StaticResource PhoneTextTitle2Style}"
Foreground="{StaticResource PhoneContrastForegroundBrush}"
Margin="12, 10, 12, 10" />
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
</toolkit:ListPicker>
</Grid>
The MultiScaleImage
is located in a second Grid
and has its Source
property bound to the Url
property of the viewmodel’s DeepZoomSource
property. Thus, changing the item in the ListPicker
switches the Deep Zoom image.
For the sake of simplicity, the logic for zooming and dragging is placed in the view code-beside. A Windows Phone Toolkit GestureListener
is placed in the Grid
as shown to monitor touch gestures, so that we can respond by zooming and dragging the MultiScaleImage
:
<Grid x:Name="ContentPanel" Grid.Row="2">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
PinchStarted="OnPinchStarted"
PinchDelta="OnPinchDelta"
DragStarted="OnDragStarted"
DragDelta="OnDragDelta" />
</toolkit:GestureService.GestureListener>
<MultiScaleImage x:Name="multiScaleImage"
Source="{Binding DeepZoomSource.Url}" />
</Grid>
The view contains a method called Zoom
, which uses the MultiScaleImage.ZoomAboutLogicalPoint
to zoom in or out of the image. We constrain the zoom level to be greater than, or equal to, half the initial size of the image, as shown:
double zoomLevel = 1;
void Zoom(double level, Point point)
{
const double minimumZoomLevel = 0.5;
if (level < minimumZoomLevel)
{
level = minimumZoomLevel;
}
multiScaleImage.ZoomAboutLogicalPoint(
level / zoomLevel, point.X, point.Y);
zoomLevel = level;
}
When the user performs a pinch gesture, it is handled in the view by recording the current zoom level of the MultiScaleImage
control. The center position of the touch gesture is determined relative to the MultiScaleImage
. The center point is calculated automatically using the two touch points involved in the pinch gesture. We then convert this pixel location to a logical location, which results in a value between 0 and 1. See the following excerpt:
void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
pinchStartLevel = zoomLevel;
Point pinchStartPoint = e.GetPosition(multiScaleImage);
pinchLogicalStartPoint
= multiScaleImage.ElementToLogicalPoint(pinchStartPoint);
}
When the user moves his fingers together or apart, the OnPinchDelta
handler is called. We then call the Zoom
method, passing it the new zoom level and the gesture reference point, like so:
void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
Zoom(e.DistanceRatio * pinchStartLevel, pinchLogicalStartPoint);
}
To allow the user to drag the image, we respond to the DragStarted
and DragDelta
events of the Toolkit’s GestureListener
. This involves recording the touch location relative to the MultiScaleImage
and the location of the top-left corner of the control, the ViewportOrigin
:
void OnDragStarted(object sender, DragStartedGestureEventArgs e)
{
dragStartPoint = e.GetPosition(multiScaleImage);
dragStartViewportOrigin = multiScaleImage.ViewportOrigin;
}
As the user performs a drag motion, the OnDragDelta
handler is called. Here we determine the relative distance from the initial touch point to the current touch location and reposition the MultiScaleImage
using its ViewportOrigin
property. The ViewportWidth
property is a logical property with a value between 0 and 1:
void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
Point touchPoint = e.GetPosition(multiScaleImage);
double visibleSize
= multiScaleImage.ActualWidth / multiScaleImage.ViewportWidth;
Point newPoint = dragStartViewportOrigin;
newPoint.X += (dragStartPoint.X - touchPoint.X) / visibleSize;
newPoint.Y += (dragStartPoint.Y - touchPoint.Y) / visibleSize;
multiScaleImage.ViewportOrigin = newPoint;
}
The sample allows the user to zoom in via a double tap. To achieve this, we subscribe to the DoubleTap
event of the Toolkit’s GestureListener
. In the handler, the location of the touch point relative to the MultiScaleImage
is calculated. This point is then translated to a logical point, which acts as a reference point to perform the zoom. The zoom level is set to twice the existing zoom level, as shown:
void OnDoubleTap(object sender, GestureEventArgs e)
{
Point tapPoint = e.GetPosition(multiScaleImage);
Point logicalPoint = multiScaleImage.ElementToLogicalPoint(tapPoint);
Zoom(zoomLevel * 2, logicalPoint);
}
Figure 7.12 shows the MultiScaleImage
displaying the Deep Zoom image generated at the beginning of this section.
By performing a pinch gesture, or by double tapping, the user is able to zoom in to explore the image in greater detail (see Figure 7.13).
Deep Zoom technology on the phone makes viewing high resolution imagery viable and allows you to leverage the built-in touch support of the phone to unlock exploration of visually rich content.
3.16.75.165