I'm struggling with the ScrollViewer
in my Windows Phone 8.1 (WinRT) app. Basically, what I'm trying to achieve is to retrieve an image using FileOpenPicker
, crop the image to a fixed ratio (square) format while letting the user select the part of the image and the zoom level, and then use that image in my app. Perfect would be functionality as in the "People" app where you can add an image to a contact, but I would settle for less if I could somehow get it to work without the ScrollView acting too erratically.
Here is one of the variations I tried:
<ScrollViewer x:Name="SelectedImageScrollViewer"
ZoomMode="Enabled"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Height="300"
Width="300" >
<Image x:Name="SelectedImage"
Source="{Binding SelectedImage}"
MinHeight="300"
MinWidth="300" />
</ScrollViewer>
and in code-behind (in the constructor):
if (SelectedImage.ActualHeight > SelectedImage.ActualWidth) {
SelectedImage.Width = SelectedImageScrollViewer.ViewportWidth;
}
else {
SelectedImage.Height = SelectedImageScrollViewer.ViewportHeight;
}
Like I said, this isn't really working, and there are several problems with it:
ScrollView
s have this kind of "rubber band" overscroll functionality built in. While I can agree on platform uniformity, here it isn't helpful, and the mentioned "People" app doesn't have that either.MaxZoomLevel
, zooming doesn't just stop, but the image drifts away and snaps back after releasing - not a good user experience.ScrollView
does not show the center of the image.How can I fix those issues, and what would be the best approach for cropping and scaling the image? Would be nice if this was available as part of the SDK as it was in Silverlight (photo chooser).
The following solution provides a reasonably good user experience. Regarding the list of problems:
ScrollViewer
.MinZoomFactor
of 1 ensures that the image always fills the frame.ScrollView
's offsets can be set in code behind.Setting IsScrollInertiaEnabled
and IsZoomInertiaEnabled
to false
removes some of the erratic behavior in scrolling an zooming. Image width and height are set in SelectedImage_SizeChanged
because the initial actual dimensions are not available in the constructor (before the page is rendered).
<ScrollViewer Grid.Row="1"
x:Name="SelectedImageScrollViewer"
ZoomMode="Enabled"
IsScrollInertiaEnabled="False"
IsZoomInertiaEnabled="False"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Height="300"
Width="300"
MinZoomFactor="1.0"
MaxZoomFactor="10.0">
<Image x:Name="SelectedImage"
Source="{Binding SelectedImage}"
HorizontalAlignment="Center"
SizeChanged="SelectedImage_SizeChanged" />
</ScrollViewer>
and
private void SelectedImage_SizeChanged(object sender, SizeChangedEventArgs e) {
// Here the proportions of the image are known and the initial size can be set
// to fill the cropping frame depending on the orientation of the image.
if (!_imageProportionsSet) {
if (SelectedImage.ActualWidth != 0) {
double actualHeight = SelectedImage.ActualHeight;
double actualWidth = SelectedImage.ActualWidth;
double viewPortWidth = SelectedImageScrollViewer.ViewportWidth;
double viewPortHeight = SelectedImageScrollViewer.ViewportHeight;
if (actualHeight > actualWidth) {
SelectedImage.Width = viewPortWidth;
double yOffset = (actualHeight - actualWidth) * viewPortWidth / actualHeight;
SelectedImageScrollViewer.ChangeView(0, yOffset, 1);
}
else {
SelectedImage.Height = viewPortHeight;
double xOffset = (actualWidth - actualHeight) * viewPortHeight / actualWidth;
SelectedImageScrollViewer.ChangeView(xOffset, 0, 1);
}
// Do this only once.
_imageProportionsSet = true;
}
}
}
This is workable. If you find any issues with this please don't hesitate to comment or to provide an improved answer.