Example of how to create an image with clickable regions - xamarin

This is not a question, but an article on how to do this.
All the details and focus will be in the answer to this question.
What is it about:
I was looking for a way to achieve is this
This is what I call a image with clickable regions, it seemed so simple but it took me some doing
What I found about this topic:
I found many questions about this on stack and other sites, but nothing about how it is actually done. Only bits and pieces scattered around.
I noticed SkiaSharp being mentioned a few times, so I searched on how to do this using that library, but again no actuall answer.
So I looked at their documentation and examples, what I needed was in there but it's hard to find if you don't know what methods and terminology to search for.
Also I had never worked with vector images before this, that was completely new for me, so I did not knew that was what I had to search on.
What I have came up with
So I took some time to figure it out, had to learn about vector images, about the skiasharp package, and lots of other stuff.
I managed to get it working, and I decided to write down here all the steps that are needed, so other people don't have to do that same search. It is all in my answer on this question.
The example in my answer is done with xamarin forms using c# and the SkiaSharp package
You can see a step by step approach in detail on how to do this, hopefully other people that are searching for this can use this as a starting point.

I had to do a lot of searching, researching and experimenting on how to do this.
Maybe there are better ways of doing this, I don't know, but this method sure works and I want to share my findings here so other people can benefit from my struggles.
What I want to achieve is this
Looks not so bad hey, let's see how this is done
So, what do you need for this ?
You have to get the SkiaSharp package from nuget, this can be easily installed using the nuget manager, search for SkiaSharp.Views.Forms
Next you need an image that you can use as your base image, in this example the image of the car you can see in the gif above.
The XAML file
The xaml file is actually very simple, in the example above I need a label on top, a Skia Canvas in the middle, and 2 buttons with a label between them at the bottom
<StackLayout VerticalOptions="Start" HorizontalOptions="FillAndExpand" Orientation="Horizontal" Margin="1, 1">
<Label Text="SELECT DAMAGE REGION"
VerticalOptions="StartAndExpand" HorizontalOptions="FillAndExpand" >
</Label>
</StackLayout>
<skia:SKCanvasView
x:Name="sKCanvasViewCar"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
EnableTouchEvents="True" >
</skia:SKCanvasView>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" VerticalOptions="End" Padding="5, 5">
<Button x:Name="ButtonSelectFromCarBack"
WidthRequest="150"
HorizontalOptions="Start"
Text="Back" />
<Label x:Name="labelRegionCar"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
<Button x:Name="ButtonSelectFromCarSelect" IsEnabled="false"
WidthRequest="150"
HorizontalOptions="End"
Text="Next" />
</StackLayout>
To avoid the error The type 'skia:SKCanvasView' was not found have this in the definition of the form xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
The code behind
In the code behind we need to do 3 things
draw the base image on the screen
determine that someone clicked on the image, and where on the image he clicked
draw the clicked region with a different color (red in this example) over the base image
1. draw the base image on the screen
My base image is a file called draw_regions_car.png that I have in my project as embedded resource
To draw it on the Skia Canvas I need put it on a SKBitmap that I can use later in the event PaintSurface to draw it on the screen.
In my example this looks like this
namespace yourProject.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PageDamageSelectFromCarImage : ContentPage
{
private SKBitmap _bitmap;
private SKMatrix _matrix = SKMatrix.CreateIdentity();
private float _scale = 1f;
private float _x;
private float _y;
bool _firstTime = true;
string _region = "";
private SKPath _path_10;
private SKPath _path_11;
public PageDamageSelectFromCarImage()
{
InitializeComponent();
sKCanvasViewCar.PaintSurface += OnCanvasViewPaintSurface;
sKCanvasViewCar.Touch += SKCanvasView_Touch;
// put the car_region image from the resources into a skia bitmap, so we can draw it later in the Surface event
string resourceID = "yourProject.Resources.draw_regions_car.png";
var assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
_bitmap = SKBitmap.Decode(stream);
}
}
}
and this is how the surface event looks like for now
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
if (_firstTime)
{
_firstTime = false;
_scale = (float)info.Width / _bitmap.Width;
}
canvas.Scale(_scale);
canvas.Translate(new SKPoint(_x, _y));
_matrix = canvas.TotalMatrix;
canvas.DrawBitmap(_bitmap, 0, 0);
using (SKPaint paint = new SKPaint())
{
// here we will draw the selected regions over the base image
}
}
If you would run the app now, it should display the base image on the screen.
From this we can get to the next steps
2. determine that someone clicked on the image, and where on the image he clicked
To do this, we need to use the Touch event of the SKCanvasView (see the ctor in this article)
In our example we called it SKCanvasView_Touch
There I determine what kind of touch has happened, and if it was a click then I call a private method called OnPressed
private void SKCanvasView_Touch(object sender, SKTouchEventArgs e)
{
switch (e.ActionType)
{
case SKTouchAction.Pressed:
OnPressed(sender, e.Location);
break;
}
}
So in the OnPressed method we can handle all the clicks that are done on the image.
For example, let's see how I can know if the user clicked on the front_door_left.
To do that, I need a path of this door.
What is this path??
Well, it is a vector drawing. A vector drawing (find in .svg files) is a series of commands that when executed results in the image.
for our front-door-left these commands could look like this
"M77.5 289.7c.3 3.2 1.9 37.3 3.6 75.8 1.7 38.5 3.2 70.1 3.4 70.3.1.1 5.6.7 12.1 1.1 6.6.5 28 2.1 47.7 3.7 19.6 1.5 36 2.5 36.3 2.2.3-.3-.6-22.6-2-49.4-1.5-26.9-2.6-49.7-2.6-50.7 0-1.4-.9-1.7-4.8-1.7-7.1 0-8.2-1.6-8.2-12.3v-8.5l-3.7-1.3c-5.3-1.6-6.3-2.7-6.3-6.3 0-6 .9-6.3 10.7-2.8 4.9 1.8 9.2 3.2 9.6 3.2.9 0 .9-17.2-.1-18.5-.4-.6-3.6-3.2-7.1-5.8l-6.5-4.7H77l.5 5.7zm90.8 124.2c.4 6.3.6 12 .3 12.7-.3.8-1.9 1.4-3.6 1.4h-3v-26.1l2.8.3 2.7.3.8 11.4z"
What they mean is not important for this example, what we need to do is create a variable of type SKPath that holds this value, and with that variable we can instruct Skia to do its magic for us.
SKPath _path_10;
_path_10 = SKPath.ParseSvgPathData("M77.5 289.7c.3 3.2 1.9 37.3 3.6 75.8 1.7 38.5 3.2 70.1 3.4 70.3.1.1 5.6.7 12.1 1.1 6.6.5 28 2.1 47.7 3.7 19.6 1.5 36 2.5 36.3 2.2.3-.3-.6-22.6-2-49.4-1.5-26.9-2.6-49.7-2.6-50.7 0-1.4-.9-1.7-4.8-1.7-7.1 0-8.2-1.6-8.2-12.3v-8.5l-3.7-1.3c-5.3-1.6-6.3-2.7-6.3-6.3 0-6 .9-6.3 10.7-2.8 4.9 1.8 9.2 3.2 9.6 3.2.9 0 .9-17.2-.1-18.5-.4-.6-3.6-3.2-7.1-5.8l-6.5-4.7H77l.5 5.7zm90.8 124.2c.4 6.3.6 12 .3 12.7-.3.8-1.9 1.4-3.6 1.4h-3v-26.1l2.8.3 2.7.3.8 11.4z");
I will explain later how you can extract this Path for every region from your base image, for now let's focus on how to use this path.
In the OnPressed method I can use this path to find out if the user clicked on this door or not, and if he did, then I will put code '10' in the private variabel _region
After that, I call InvalidateSurface to fire the Surface OnCanvasViewPaintSurface event again, where we do all our drawing
// I define this (and all other paths) on top of the class, so they are in the global scope for this class
SKPath _path_10;
private async void OnPressed(object sender, SKPoint point)
{
SKPoint location = point;
_region = "";
if (_path_10.Contains(location.X, location.Y))
{
_region = "10";
}
sKCanvasViewCar.InvalidateSurface();
}
3. draw the clicked region with a different color (red in this example) over the base image
Let's look at the event OnCanvasViewPaintSurface again with the extra code
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
if (_firstTime)
{
_firstTime = false;
_scale = (float)info.Width / _bitmap.Width;
}
canvas.Scale(_scale);
canvas.Translate(new SKPoint(_x, _y));
_matrix = canvas.TotalMatrix;
canvas.DrawBitmap(_bitmap, 0, 0);
_path_10 = SKPath.ParseSvgPathData("m95 187.7-3.2 5.8-3.4 4.2-3.4 4.1-4.7 3.5-4.8 3.5-7 2.5-7 2.5-.3 33.6L61 281l2.2 1 2.3.9 20.5 3.6 20.5 3.6 55.5-.3 55.5-.3v-67l-1.9-4-2-4-16-16.3-16.1-16.2H98.2l-3.2 5.7zm85.3 2.7 3.3 1.3 13.9 14.7 14 14.7 1.5 2.7 1.5 2.7.3 21.1.3 21.1-1.5 3.7-1.5 3.7-3.5 1.4-3.5 1.5h-46.2l-2.9-1.5-2.9-1.5-.9-1.8-.9-1.7-.7-7.5-.7-7.5-3.5-28.2-3.5-28.3 1.5-2.7 1.4-2.8 2.4-2.2 2.3-2.2 2.5-1 2.5-1h10.8l10.8-.1 3.2 1.4zm-35.5 71.8 1.2 1.2V277l-2.5.6-2.5.6-2.5-.6-2.5-.6v-14.8l1.3-.5 1.2-.5 2.6-.1 2.5-.1 1.2 1.2z");
_path_10.Transform(_matrix);
using (SKPaint paint = new SKPaint())
{
if (_region == "10")
{
DrawRegion(canvas, paint, _path_10);
}
}
}
private void DrawRegion(SKCanvas canvas, SKPaint paint, SKPath path, bool strokeOnly = false, int strokeWidth = 1)
{
path.Transform(_matrix.Invert());
if (strokeOnly == false)
{
paint.Style = SKPaintStyle.StrokeAndFill;
}
paint.Color = (Xamarin.Forms.Color.Red).ToSKColor();
paint.StrokeWidth = strokeWidth;
canvas.DrawPath(path, paint);
}
This you need to repeat for every region in the base image you want to be clickable
private async void OnPressed(object sender, SKPoint point)
{
SKPoint location = point;
_region = "";
if (_path_10.Contains(location.X, location.Y))
{
_region = "10";
}
else if (_path_11.Contains(location.X, location.Y))
{
_region = "11";
}
// and so on...
sKCanvasViewCar.InvalidateSurface();
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
...
_path_10 = SKPath.ParseSvgPathData("m95 187.7-3.2 5.8-3.4 4.2-3.4 4.1-4.7 3.5-4.8 3.5-7 2.5-7 2.5-.3 33.6L61 281l2.2 1 2.3.9 20.5 3.6 20.5 3.6 55.5-.3 55.5-.3v-67l-1.9-4-2-4-16-16.3-16.1-16.2H98.2l-3.2 5.7zm85.3 2.7 3.3 1.3 13.9 14.7 14 14.7 1.5 2.7 1.5 2.7.3 21.1.3 21.1-1.5 3.7-1.5 3.7-3.5 1.4-3.5 1.5h-46.2l-2.9-1.5-2.9-1.5-.9-1.8-.9-1.7-.7-7.5-.7-7.5-3.5-28.2-3.5-28.3 1.5-2.7 1.4-2.8 2.4-2.2 2.3-2.2 2.5-1 2.5-1h10.8l10.8-.1 3.2 1.4zm-35.5 71.8 1.2 1.2V277l-2.5.6-2.5.6-2.5-.6-2.5-.6v-14.8l1.3-.5 1.2-.5 2.6-.1 2.5-.1 1.2 1.2z");
_path_11 = SKPath.ParseSvgPathData("m52.2 356.7.3 59.8h165v-119l-82.8-.3L52 297l.2 59.7zm159.4-54.8 3.1 1.9.7 3.2.6 3.2v92.3l-1 3.6-1 3.7-2.5 1.6-2.4 1.6h-45.9l-4.1-4-4.1-4v-96.5l1.9-2.9 2-3 2.3-1.2 2.3-1.3 22.5-.1h22.5l3.1 1.9zM145.5 314v7.5l-4.7.3-4.8.3v-14.8l.7-.7.7-.7 4.1.3 4 .3v7.5z");
_path_10.Transform(_matrix);
_path_11.Transform(_matrix);
using (SKPaint paint = new SKPaint())
{
if (_region == "10")
{
DrawRegion(canvas, paint, _path_10);
}
if (_region == "11")
{
DrawRegion(canvas, paint, _path_11);
}
// and so on...
}
How to extract a path from my base image ?
My base image draw_regions_car.png is a simple png image, which is not a vector image and thus has no paths.
So here is how I extract the path for the front-door-left from this image.
First I open it in an application that is able to do complex selection, I use the free program paint.net for this.
In there I select Tools/Magic Wand and drop it on the door, so it becomes selected, then I click on ctrl-I to revert the selection and then click on delete.
Now save this file, I saved it as 10.png
Next step is to convert this to svg and for that I use the website https://svgco.de/
Now you have a .svg file you can open with any text editor, the content looks like this
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 688 833">
<path fill="#007CFF" d="M77.5 289.7c.3 3.2 1.9 37.3 3.6 75.8 1.7 38.5 3.2 70.1 3.4 70.3.1.1 5.6.7 12.1 1.1 6.6.5 28 2.1 47.7 3.7 19.6 1.5 36 2.5 36.3 2.2.3-.3-.6-22.6-2-49.4-1.5-26.9-2.6-49.7-2.6-50.7 0-1.4-.9-1.7-4.8-1.7-7.1 0-8.2-1.6-8.2-12.3v-8.5l-3.7-1.3c-5.3-1.6-6.3-2.7-6.3-6.3 0-6 .9-6.3 10.7-2.8 4.9 1.8 9.2 3.2 9.6 3.2.9 0 .9-17.2-.1-18.5-.4-.6-3.6-3.2-7.1-5.8l-6.5-4.7H77l.5 5.7zm90.8 124.2c.4 6.3.6 12 .3 12.7-.3.8-1.9 1.4-3.6 1.4h-3v-26.1l2.8.3 2.7.3.8 11.4z"/>
<path fill="#0000CB" d="M77.2 291.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm97 20.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm-96 1.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm1 22c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm97 15.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm-96 6.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm97 12.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm-96 9.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm97 10.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm-96 11.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm97 7.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm-10.9 12.5c0 2.2.2 3 .4 1.7.2-1.2.2-3 0-4-.3-.9-.5.1-.4 2.3zm-85.1 2c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm79 1.5c0 1.4.2 1.9.5 1.2.2-.6.2-1.8 0-2.5-.3-.6-.5-.1-.5 1.3zm18 3.5c0 1.6.2 2.2.5 1.2.2-.9.2-2.3 0-3-.3-.6-.5.1-.5 1.8zm-92.4 8.2c.7.3 1.6.2 1.9-.1.4-.3-.2-.6-1.3-.5-1.1 0-1.4.3-.6.6zm26 2c.7.3 1.6.2 1.9-.1.4-.3-.2-.6-1.3-.5-1.1 0-1.4.3-.6.6zm40 3c.7.3 1.6.2 1.9-.1.4-.3-.2-.6-1.3-.5-1.1 0-1.4.3-.6.6zm13 1c.7.3 1.6.2 1.9-.1.4-.3-.2-.6-1.3-.5-1.1 0-1.4.3-.6.6z"/>
</svg>
And there you will find the path that you need in your code
Conclusion
Maybe there are better ways to do this, but at least this methods works.
It is not even so complicated once you realize how this works, it just takes some time to set it all up.
For all you people desperate looking on the web on how to do this, I hope you can all use this as a starting point.

Related

Xamarin Forms - Frame change after update Xamarin Forms version

I used to use the Frame control of Xamarin.Forms as a CardView for Android, but I updated the Xamarin.Forms package to version 3.1.0 and the frame appearence has changed. It doesn't have elevation and shadows. I tried to create a custom renderer, but doesn't work.
To explain what I'm trying to say, see the pictures below. First picture is the old behaviour of frame. The appearence is exactly like CardView in Android and the second picture is the new behaviour, for the version 3.1.0 of Xamarin.Forms. The frame doesn't have elevation and shadows.
Old behaviour:
New behaviour
Here is my code that I use the Frame (for Android Platform, CardView is a simple frame)
<customViews:CardView>
<StackLayout Spacing="0">
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness" Android="16"/>
</StackLayout.Margin>
<ContentView>
<OnPlatform x:TypeArguments="View">
<OnPlatform.iOS>
<customViews:HeaderDivider/>
</OnPlatform.iOS>
</OnPlatform>
</ContentView>
public class CardView : Frame
{
public CardView()
{
Padding = 0;
if (Device.RuntimePlatform == Device.iOS)
{
HasShadow = false;
OutlineColor = Color.Transparent;
BackgroundColor = Color.Transparent;
}
}
}
<Frame HasShadow="true">
<Label Text="Test"/
</Frame>
I guess after Update default value for HasShadow in Frame is set to false;

How to update pivot header spacing with Caliburn Micro and WP8

I've come across an issue using the Pivot control with Caliburn Micro and WP8. When I update DisplayName for the child ViewModels (pivot items), the spacing of the pivot headers does not update to reflect this. Thus, pivot headers overlap each other, and it looks very jumbled.
Example:
WP7 Version:
WP8 Version:
This problem only started when we migrated from targetting WP7/WP8 to WP8 only. Does anyone have any ideas about how to get the spacing to update when changing DisplayName, and thus the pivot header text? Thanks!
I know a long time has passed since you asked this, but I got a solution.
Microsoft is promising that this issue is fixed in WP8, but it still isn't.
In order to render the headers properly, you have to change the width of the pivot item.
So here is an example code width x:Name="PivotControl" set for Pivot:
double width = PivotControl.Width;
double width2 = Application.Current.Host.Content.ActualWidth+10;
PivotControl.Width = width2;
await Task.Delay(1);
PivotControl.Width = width;
This will change the width to a bit more than yoru screen width and than after 1ms change it back so the items will be rendered properly and the Layout will be updated (because LayoutUpdate is not working).
Here is better solution
Pivot header template:
<!-- The SizeChanged event is key here. We have to invalidate an ancestor when we get this event. -->
<DataTemplate x:Key="pivotHeaderTemplate">
<TextBlock SizeChanged="OnHeaderSizeChanged" Text="{Binding SomeData}"/>
</DataTemplate>
Pivot:
<phone:Pivot Title="WHATEVER"
DataContext="{StaticResource query}"
ItemsSource="{Binding source}"
HeaderTemplate="{StaticResource pivotHeaderTemplate}"
ItemContainerStyle="{StaticResource pivotItemStyle}"/>
Code-behind:
/// <summary>
/// When something inside of the template changes, then we need to invalidate the measure of the ancestor PivotHeadersControl
/// </summary>
private void OnHeaderSizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
while (fe != null)
{
if (fe is PivotHeadersControl)
{
fe.InvalidateMeasure();
break;
}
fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
}
}

Is there a way of finding out what would my orientation have been?

I'm currently writing a portrait only app, but I have a customer requirement that they'd like to implement a special feature if the phone is turned on its side.
To be clear they don't want the page to change orientation - so keeping the page as portrait works well here - but they do want to be able to detect the sideways change.
Is there anyway of finding this out (e.g. from rootframe or from some other object?) or do I have to access the Accelerometer data and work it out myself?
To be clear on this...
I'm trying to keep the page in portrait at all times.
and if I specify SupportedOrientations="portraitorlandscape" then keeping the page in portrait seems to be hard (correct me if I'm wrong, but it just doesn't seem to want to stay in portrait - the MS SDK is too good at making the page go landscape)
and if I don't specify SupportedOrientations="portraitorlandscape" then I don't get calls to OnOrientationChanged in either the page or the RootFrame
And as the icing on the cake... I need the phone to stay in portrait mode too - I need the SystemTray to stay at the top of the screen (the portrait top).
You can handle the OnOrientationChanged event which will return a PageOrientation enumeration.
Accepting this because of the comments:
#Stuart - You may find the Orientation Helper class in this starter kit useful. It uses the accelerometer, so I guess you'll have to use that, but it might save you time rolling out your own version: http://msdn.microsoft.com/en-us/library/gg442298%28VS.92%29.aspx#Customizing_Behavior
This might help, but for a case when arriving to this particular page, not for the initial page - so only partly answering the question. It trigs the OnOrientationChanged although no change has been done! (Figured out this solution after having tried to find a solution for two days) :
On the particular page, write in . xaml code
Orientation="None"
On the .xaml.cs side, write under
InitializeComponent();
Orientation = this.Orientation;
this.OrientationChanged += new EventHandler<OrientationChangedEventArgs>
(OnOrientationChanged);
and separately
void OnOrientationChanged(object sender, OrientationChangedEventArgs e)
{
if ((e.Orientation & PageOrientation.Landscape) != 0)
{
MyImage.Height = 480; //for example
}
{
MyImage.Width = 480; // for example
}
}
In my case, I placed the image as follows:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<Image x:Name="MyImage"/>
</StackPanel>
.. followed by other code, still loading during which time picture is shown ...
This decreases the size of the image when in Landscape mode when entering the page!
Got the solution, finally, after having seen Jeff Prosises site
Detect orientation change:
http://alan.beech.me.uk/2011/04/19/detecting-orientation-change-wp7dev/
I had to do a similar thing in one of my apps before where an image that is used as the background doesnt rotate but other items on the page do.
The code looks a bit like this:
protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
// Keep the image in the same position as in portrait
// But still allows other controls to rotate when orientation changes.
switch (e.Orientation)
{
case PageOrientation.LandscapeRight:
ForegroundImage.RenderTransform = new CompositeTransform { Rotation = 90 };
ForegroundImage.RenderTransformOrigin = new Point(0.5, 0.5);
ForegroundImage.Margin = new Thickness(158.592, -158.792, 158.592, -160.558);
break;
case PageOrientation.LandscapeLeft:
ForegroundImage.RenderTransform = new CompositeTransform { Rotation = 270 };
ForegroundImage.RenderTransformOrigin = new Point(0.5, 0.5);
ForegroundImage.Margin = new Thickness(158.592, -158.792, 158.592, -160.558);
break;
default: // case PageOrientation.PortraitUp:
ForegroundImage.RenderTransform = null;
ForegroundImage.RenderTransformOrigin = new Point(0, 0);
ForegroundImage.Margin = new Thickness();
break;
}
base.OnOrientationChanged(e);
}
Unfortunately there's no real work around for the system tray or app bar. For the system tray you could hide this though and then only show it (for a period of time) when the user taps or swipes near that part of the screen.

Silverlight behavior databinding

I'm following this example for an auto-scrolling behavior on a ListBox on WP7 Mango RC targeting 7.1.
In my Xaml:
<ListBox x:Name="StatusMessages"
Height="100"
ItemsSource="{Binding StatusMessages, Mode=TwoWay}"
DisplayMemberPath="Message"
Grid.Row="3">
<i:Interaction.Behaviors>
<behaviors:ListBoxItemAutoScrollBehavior FoundItem="{Binding FoundItem}" />
</i:Interaction.Behaviors>
</ListBox>
The behavior:
public class ListBoxItemAutoScrollBehavior : Behavior<ListBox>
{
public object FoundItem
{
get { return GetValue(FoundItemProperty); }
set { SetValue(FoundItemProperty, value); }
}
public static readonly DependencyProperty FoundItemProperty = DependencyProperty.Register("FoundItem", typeof (object), typeof (ListBoxItemAutoScrollBehavior), new PropertyMetadata(FoundItemChanged));
private static void FoundItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ListBoxItemAutoScrollBehavior) d).AssociatedObject.ScrollIntoView(e.NewValue);
}
}
I have a breakpoint set at the FoundItemChanged method and would expect to see it hit when I set FoundItem in my ViewModel and fire NotifyProperyChanged. Only, it doesn't work, any ideas why or what I might be doing wrong?
Thanks.
update: breakpoints are hit for OnAttached and OnDetaching in the behavior.
update 2: This works in a regular Silveright 4 application.
update 3: Using version 3.8.5.0 of System.Windows.Interactivity fixed it.
Are you targeting 7.1 / Mango? Binding to DependencyObjects, as opposed to FrameworkElements, is a Silverlight 4 feature and thus not available in 7.0 (which uses SL 3).
There's a workaround that Prism and MVVM Light use to bind to DO's in SL 3. Check out their source for details.
Edit: Your problem is your PropertyMetadata constructor arguments. By not specifying 2 arguments (or, specifically, passing a method rather than a PropertyChangedCallback instance), the compiler might be resolving the default value constructor overload.
In short, change it to:
new PropertyMetadata(null, FoundItemChanged)
Or:
new PropertyMetadata(new PropertyChangedCallback(FoundItemChanged))
Using version 3.8.5.0 of System.Windows.Interactivity fixed this problem.
This post gave me the tip: http://caliburnmicro.codeplex.com/discussions/271092

Image/Photo gallery like built-in WP7

I'm looking for a photo gallery for Windows Phone 7. Something that looks and works the same as the built-in photo viewer (slide photo's using a flick action, resize using pinch, drag). When you flick the image you can see it sliding to the next image...and snaps the list to that image.
I already built the resize and drag functionality for the images. I just can't figure out how to create the actual photo slider.
Can anyone point me into the right direction?
Things i've tried:
Pivot Viewer (doesn't work because it interferes with the drag functions of the image, haven't been able to disable the pivot viewer touch)
Plain listbox (can't find how to snap to the current image)
Thanks in advance
Actually I've implemented exactly what you are saying in one of my apps,
You need to use Silverlight Control TOolkit's gesture listener to capture Drag and Pinch from touch.
define a CompositeTransformation for your image and set it's scale (on pinch) and Offset (in drag).
Obviously when the image is not zoom, drag can trigger going to next image.
To make it feel smoother, you may want to define a storyboard on your page resources to use (instead of just settings Offset)
I hope it can help/
Drag handlers pseudo code for slider effect:
<Canvas>
<Image x:Name="imgImage" Source="{Binding ...}" Width="..." Height="...">
<Image.RenderTransform>
<CompositeTransform x:Name="imgImageTranslate" />
</Image.RenderTransform>
</Image>
</Canvas>
private void GestureListener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
{
if (e.Direction == System.Windows.Controls.Orientation.Horizontal)
{
var abs = Math.Abs(PANEL_DRAG_HORIZONTAL);
if (abs > 75)
{
if (PANEL_DRAG_HORIZONTAL > 0) // MovePrevious;
else //MoveNext();
e.Handled = true;
}
}
}
double PANEL_DRAG_HORIZONTAL = 0;
private void GestureListener_DragDelta(object sender, DragDeltaGestureEventArgs e)
{
if (e.Direction == System.Windows.Controls.Orientation.Horizontal)
{
PANEL_DRAG_HORIZONTAL += e.HorizontalChange;
var baseLeft = -imgImage.Width / 2;
if (PANEL_DRAG_HORIZONTAL > 75) imgImageTranslate.OffsetX = baseLeft + PANEL_DRAG_HORIZONTAL;
else if (PANEL_DRAG_HORIZONTAL < -75) imgImageTranslate.OffsetX = baseLeft + PANEL_DRAG_HORIZONTAL;
else imgImageTranslate.OffsetX = baseLeft;
}
}
}
private void GestureListener_DragStarted(object sender, DragStartedGestureEventArgs e)
{
PANEL_DRAG_HORIZONTAL = 0;
}
What about using a ScrollViewer with horizontal orientation? Of course, you will have to manually detect user actions and implement the proper response (with a couple of properly set Storyboards).
Even a better approach would be writing your own custom control that will view images. A good place to start is this - a CoverFlow control in Silverlight. Once you get the idea how you can bind your image collection to a custom control, all you need is handle user gestures on the currently selected item.

Resources