Displaying an image with Xamarin Forms - xamarin

Solved: The answer was to update all of the nuget packages and target a newer version of Android. Now images loads as expected. I'm not happy with this as I was using exactly the code that Xamarin provided and targeting newer versions has deprecated some of the items the code relys on. Initial version was Xamarin.Forms v23 and I updated to V25
I have a brand new Xamarin forms project with a simple view in which I'm trying to display an image. I've tried several ways of getting an image to display and I am not having any luck at all.
I'm using <image> and I have also tried FFImageLoader control as well.
<StackLayout Orientation="Vertical">
<ff:CachedImage Source="https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg" WidthRequest="100" HeightRequest="100" />
<Button x:Name="btn" Text="Image" Clicked="Button_Clicked" />
<Frame OutlineColor="Red">
<Image x:Name="StupidImage" Source="{Binding Thumbnail}" Aspect="Fill" HeightRequest="100" WidthRequest="100" />
</Frame>
</StackLayout>
This is the current view. I've also set the Source directly to a value with no result.
I'm able to get a stream for the image. I'm able to read all of the bytes from the stream. I built a debug visualizer to display the bytes as an image. Getting the image from a source is not a problem. Getting the image control(s) to display the image is a problem.
I tried binding with a view model. When that failed, I tried that directly setting the source
StupidImage.Source = ImageSource.FromStream(() => result.Stream);
I also made a copy of the bytes and tried
StupidImage.Source = ImageSource.FromStream(() => new MemoryStream(imageBytes));
I've tried ImageSource.FromFile() and .FromUri. I tried adding an image to the project as a resource. Each try was the same, the resource was read and the bytes were available, but the image control just doesn't display it.
I thought maybe it was a size problem, so I set the size of the control. Nothing. I thought maybe it was a resolution problem, so I used a smaller image. I tried several different images of varying quality.
Then I gave up on the image control and I got the FFImageLoading nuget package and gave it a direct url to an image. Same example that FFImageLoading examples used. Still no image.
I tried the emulator and I tried 2 different physical devices. Same result.
I also tried setting an image on a button using btn.Image = "whatever.jpg" with the same result.
This is the result every time. I'm lost. How do I get images to display?
EDIT:
I did get this to work, but only on the emulator
<Image x:Name="StupidImage" Source="https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg" />
and same for
StupidImage.Source = ImageSource.FromUri(new Uri("https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg"));
EDIT 2 - Clarification
My goal is to allow the user to select a photo from the device and then display a preview of it.

If you want to use images in you app you can load them into your Shared Project, like
Make sure you change the Build Action to Embedded resource
Then in your code
image.Source = ImageSource.FromResource("App5.Images.useravatar.png");
Note the Resource name.
And XAML
<ContentPage.Content>
<StackLayout>
<Image x:Name="image" WidthRequest="50"/>
</StackLayout>
</ContentPage.Content>

Just a few things you can take off the list:
[x] Adding a image from Visual studio :
Right click on the correct folder
select Add >> New File ...
NB: you have to add it with visual studio and not just throw it in the folder. Visual studio needs to know about it
[x] When Adding the image is it in the correct place :
For android: it has to be in
ProjectName.Driod.Resources.drawable folder
For ios: it has to be in
ProjectName.iOS.Resources folder
[x] Naming Convention
Its always best to use .png , all lowercase , no spaces or special char on both android and ios
with ios you normally get 3 images of the same image with the following namting convention
theman.png
theman#2x.png
theman#3x.png
They are all the same image just different sizes
[x] Showing it in xaml :
<StackLayout>
<Image Source="thedog.png" HeightRequest="100" WidthRequest="100" />
</StackLayout>
In your example you used a frame , how about a stacklayout ? a frame has more requirements.
for MVVM you can change Source with the following , dont forget that twoway :)
Source="{Binding Thumbnail, Mode=TwoWay}"
NB This is VERY basic explanations

You can try implementing the CrossMedia Plugin.
Then in your button clicked code section, put the following:
Button_Clicked.Clicked += async (sender, args) =>
{
if ( !CrossMedia.Current.IsPickPhotoSupported )
{
DisplayAlert("Error message here", "More message", "OK");
return;
}
var file = await Plugin.Media.CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium
});
if (file == null)
return;
image.Source = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
});
};
Once the button is clicked, the gallery/directory will be displayed. You can choose the photo you want. Once you hit OK the image will be displayed in the Image control/tag. I'm not sure if this is the solution you are looking for. Hopes it gets you on the right direction.

This may or may not help I'll add some code, one of the surprising things about Xamarin forms and Android and using a memory stream.. is that the device density multiplier is still applied even if you aren't using a resource(If I am remembering correctly) so I would imagine if you are looking at the ADB interface you will see memory issues which is why you cant display an image... I solved this previously via sampling
The way I solved it was creating a new Image subclass -ResourceImage,
public class ResourceImage :Image
{
public enum SourceTypes{
Database,
File,
Function,
}
private bool _LoadAct = false;
public bool LoadAct { get{
return _LoadAct;
}
set{ _LoadAct = value; OnPropertyChanged ("LoadAct");
}
}
public Func<Stream> Func{ get; set; }
public SourceTypes SourceType{ get; set;}
public string ResName{ get; set;}
public ResourceImage ()
{
}
public ResourceImage (string name)
{
ResName = name;
}
public ResourceImage(Func<Stream> func){
SourceType = SourceTypes.Function;
Func = func;
}
}
then in the Android Renderer : I did the following
public class ResourceImageRenderer : ImageRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Image> e)
{
base.OnElementChanged (e);
if (e.OldElement == null)
{
var el = (ResourceImage)Element;
if (el.SourceType == ResourceImage.SourceTypes.Database) {
//Ignore for now
} else if (el.SourceType == ResourceImage.SourceTypes.File) {
using (global::Android.Graphics.BitmapFactory.Options options = new global::Android.Graphics.BitmapFactory.Options ()) {
options.InJustDecodeBounds = false;
options.InSampleSize = 1;//calculateInSampleSize (options, outS.X / 4, outS.Y / 4);
var gd = Context.Resources.GetIdentifier (el.ResName.Split (new char[]{ '.' }) [0], "drawable", Context.PackageName);
using (global::Android.Graphics.Rect rt = new global::Android.Graphics.Rect (0, 0, 0, 0)) {
var bitmap = global::Android.Graphics.BitmapFactory.DecodeResource (Context.Resources, gd, options);//DecodeStream (ms, rt, options);
bitmap.Density = global::Android.Graphics.Bitmap.DensityNone;
Control.SetImageDrawable (new global::Android.Graphics.Drawables.BitmapDrawable (bitmap));
}
}
} else if (el.SourceType == ResourceImage.SourceTypes.Function) {
new Task (() => {
var ms = el.Func();
if(ms == null)return;
global::Android.Graphics.BitmapFactory.Options options = new global::Android.Graphics.BitmapFactory.Options ();
options.InJustDecodeBounds = false;
options.InSampleSize = 2;//calculateInSampleSize (options, outS.X / 4, outS.Y / 4);
ms.Position = 0;
Device.BeginInvokeOnMainThread(()=>{
using (global::Android.Graphics.Rect rt = new global::Android.Graphics.Rect (0, 0, 0, 0)) {
try{
var bitmap = global::Android.Graphics.BitmapFactory.DecodeStream (ms, rt, options);
bitmap.Density = global::Android.Graphics.Bitmap.DensityNone;
Control.SetImageDrawable (new global::Android.Graphics.Drawables.BitmapDrawable (bitmap));
}catch(Exception eee){
}
}
});
}).Start();
}
}
}
Looking back at the code(haven't touched it in years.) there are plenty of places for improvement, I had to add the sampling to solve the same issue , users were selecting images to display in a messaging app and it worked perfectly on iOS just never displayed on Android

This is how I allow a user to select an image and then display it on a page.
I call my image service Select Image method passing in a callback method
await _imageService.SelectImage(ImageSelected);
This is my SelectImage method. There is some permission checking at the start. It uses the Media Plugin to display the gallery and allow the user to select an image.
public async Task SelectImage(Action<MediaFile> imageAction)
{
var allowed = await _permissionService.CheckOrRequestStoragePermission();
if (!allowed) return;
if (!_media.IsPickPhotoSupported)
{
throw new GalleryUnavailableException("Gallery unavailable");
}
var file = await _media.PickPhotoAsync(new PickMediaOptions
{
PhotoSize = PhotoSize.Small,
CompressionQuality = 92
});
imageAction(file);
}
It returns a MediaFile
Here is the Image Selected callback method
private void ImageSelected(MediaFile image)
{
if (image == null)
{
return;
}
ChosenImage = new IncidentImage
{
ImageBytes = image.ToByteArray()
};
}
ChosenImage is a Property in my view model
public IncidentImage ChosenImage {get; set;}
I use PropertyChanged.Fody to trigger property changed notifications but you can also use INotifyPropertyChanged.
And IncidentImage is a class I use to both store and display images
public class IncidentImage
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int IncidentDetailsId { get; set; }
public byte[] ImageBytes { get; set; }
[Ignore]
public ImageSource ImageSource
{
get
{
ImageSource retval = null;
try
{
if (ImageBytes != null)
{
retval = ImageSource.FromStream(() => new MemoryStream(ImageBytes));
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return retval;
}
}
}
And here is the XAML
<Image Source="{Binding ChosenImage.ImageSource}"
Aspect="AspectFit"/>

Related

Carousal view autoslide after content load - Xamarin.forms

I have a carousal view in my xamarin.forms app. I am trying to implement a feature just like stories in instagram. That is when we tap any of story, it will show inside a carousal sort of view and auto slide to next story after certain seconds.
I can auto slide the carousal view after certain seconds using timer. The problem I am facing is since the content of carousal items is loaded from URL (eg: image), the view will auto slide after the predefined time without the loading the content from URL. How can I tackle this problem, ie: the carousal should only slide after the content is loaded. Any help is really appreciated.
Current auto slide implementation
Device.StartTimer(TimeSpan.FromSeconds(7), (Func<bool>)(() =>
{
Device.BeginInvokeOnMainThread(() =>
{
StatusCarousal.Position = (StatusCarousal.Position + 1) % statusList.Count;
});
return false;
}));
EDIT
My current progress.
I have created an auto sliding carousalview using Behaviors like this.
public class AutoscrollCarouselBehavior : Behavior<CarouselView.FormsPlugin.Abstractions.CarouselViewControl>
{
public int Delay { get; set; } = 7000;
private bool runTimer;
private CarouselViewControl attachedCarousel;
protected override void OnAttachedTo(CarouselViewControl bindable)
{
base.OnAttachedTo(bindable);
runTimer = true;
attachedCarousel = bindable;
Device.StartTimer(TimeSpan.FromMilliseconds(Delay), () =>
{
MoveCarousel();
return runTimer;
});
}
protected override void OnDetachingFrom(CarouselViewControl bindable)
{
runTimer = false;
base.OnDetachingFrom(bindable);
}
void MoveCarousel()
{
if (attachedCarousel.ItemsSource != null)
{
if (attachedCarousel.Position < attachedCarousel.ItemsSource.GetCount() - 1)
{
attachedCarousel.Position++;
}
else
{
attachedCarousel.Position = 0;
}
}
}
}
Xaml
<cv:CarouselViewControl x:Name="StatusCarousal"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource personDataTemplateSelector}"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" >
<cv:CarouselViewControl.Behaviors>
<local:AutoscrollCarouselBehavior />
</cv:CarouselViewControl.Behaviors>
</cv:CarouselViewControl>
And also added additional properties for progressbar to progress.
public static class AttachedProperties
{
public static BindableProperty AnimatedProgressProperty =
BindableProperty.CreateAttached("AnimatedProgress",
typeof(double),
typeof(ProgressBar),
0.0d,
BindingMode.OneWay,
propertyChanged: (b, o, n) =>
ProgressBarProgressChanged((ProgressBar)b, (double)n));
private static void ProgressBarProgressChanged(ProgressBar progressBar, double progress)
{
ViewExtensions.CancelAnimations(progressBar);
progressBar.ProgressTo((double)progress, 800, Easing.SinOut);
}
}
Xaml
<ProgressBar HeightRequest="1.5"
x:Name="StatusProgressBar"
local:AttachedProperties.AnimatedProgressAnimationTime=""
local:AttachedProperties.AnimatedProgress=""
HorizontalOptions="FillAndExpand" VerticalOptions="Start"
ProgressColor="Snow" Margin="10,5,10,0"></ProgressBar>
The two problem still I am facing is
How to connect the carousal view auto scroll according to the ffimage loading
from api? ie; only start the auto scroll after loading the image properly ?
How to connect the carousalview to the progressbar progress preoperty? so
that both the progressbar will load into full and the carousal gets slide?

Webview text field gets scrolled to top when trying to focus - Xamarin.forms iOS

I have xamarin forms iOS app contains a webview. The webview load a html page contains some text fields.The problem I am facing is whenever we click on the text field,the keyboard shows and the entire web page will scroll to top and the text field will become invisible.When we open the same page on a browser it works fine. I have used xam.plugins.forms.keyboard overlap for my other non webview pages. How can I fix this issue? I want the textview field should be there when we focus it .
I have seen this similar problem on this question. But it didn't solved my issue. Any help is appreciated.
My XAML
<ContentPage.Content>
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView x:Name="Webview"
HeightRequest="1000"
WidthRequest="1000"
IsVisible="False"
Navigating="OnNavigating"
Navigated="OnNavigated"
VerticalOptions="FillAndExpand"/>
</Grid>
</ContentPage.Content>
You can remove the plugin and implement it by yourself.
For example, you can check the source code and copy it to your project as a PageRenderer.
In the page renderer, I add a bool value to check whether you need to use this renderer or not. Here I use the ClassId to check:
bool useThisRenderer;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Page p = e.NewElement as Page;
if (p.ClassId == "0")
{
useThisRenderer = false;
}
else
{
useThisRenderer = true;
}
}
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (useThisRenderer)
{
var page = Element as ContentPage;
if (page != null)
{
var contentScrollView = page.Content as ScrollView;
if (contentScrollView != null)
return;
RegisterForKeyboardNotifications();
}
}
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
if (useThisRenderer)
{
UnregisterForKeyboardNotifications();
}
}
Then in your Xamarin.forms project, if one of your content page does not need this renderer(for example the current page you have a webview), you can set the classid = "0" to avoid this:
public MainPage()
{
InitializeComponent();
this.ClassId = "0";
}
If you need this renderer, then do nothing about this.ClassId.
I upload a sample project here and feel free to ask me any question.

How do you allow users to copy and paste from an Xamarin.Forms label

How do you allow users to copy and paste from an Xamarin.Forms Label?
Click on the text on any platform the default settings don't allow highlighting and therefore copying and pasting.
Any help would be appreciated.
What you could do is wrap your label in a gesture recogniser:
<Label Text="Test">
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="YourFunctionToHandleMadTaps"
NumberOfTapsRequired="1"
/>
</Label.GestureRecognizers>
</Label>
This will trigger your function and in that function you can get to the clipboard and copy and paste. However I haven't been able to find an easy way to access the clipboard in Xamarin.Forms so you have to use the dependency service.
Xamarin.Forms Dependency service documentation
Here is how I did my clipboard data access. Please note that in my project I only needed to nab data from the clipboard so this code just shows you how to access the clipboard data:
Create an interface in you X.F project eg:
public interface IClipBoard
{
String GetTextFromClipBoard();
}
Implement the interface in your mobile projects:
Android:
public string GetTextFromClipBoard ()
{
var clipboardmanager = (ClipboardManager)Forms.Context.GetSystemService (Context.ClipboardService);
var item = clipboardmanager.PrimaryClip.GetItemAt(0);
var text = item.Text;
return text;
}
iOs:
public string GetTextFromClipBoard ()
{
var pb = UIPasteboard.General.GetValue ("public.utf8-plain-text");
return pb.ToString ();
}
Don't forget to add the Assembly bits at the top:
iOs: [assembly: Dependency (typeof (ClipBoard_iOs))]
Android: [assembly: Dependency (typeof (ClipBoard_Droid))]
Call the dependency service from you X.F function
public void YourFunctionToHandleMadTaps(Object sender, EventArgs ea)
{
var clipboardText = DependencyService.Get<IClipBoard> ().GetTextFromClipBoard ();
YourFunctionToHandleMadTaps.Text = clipboardText;
}
Since I_Khanage provided only a half solution, I will post the full solution.
IClipboardService should be implemented for all the targeting platforms, in my case it is Android and iOS:
public interface IClipboardService
{
string GetTextFromClipboard();
void SendTextToClipboard(string text);
}
// iOS
public class ClipboardService : IClipboardService
{
public string GetTextFromClipboard() => UIPasteboard.General.String;
public void SendTextToClipboard(string text) => UIPasteboard.General.String = text;
}
// Android
public class ClipboardService : IClipboardService
{
public string GetTextFromClipboard()
{
var clipboardmanager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
var item = clipboardmanager.PrimaryClip.GetItemAt(0);
var text = item.Text;
return text;
}
public void SendTextToClipboard(string text)
{
// Get the Clipboard Manager
var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
// Create a new Clip
var clip = ClipData.NewPlainText("YOUR_TITLE_HERE", text);
// Copy the text
clipboardManager.PrimaryClip = clip;
}
}
The code is available on github.
Now just add a GestureRecognizer in order to trigger the tap event.
P.S.: Clipboard service is now available as a part of Xamarin.Essentials package, so there is no need to write one yourself.

App crashes while trying to bind the Isolated Storage Image

In my application I am using the below mentioned helper method for binding my Isolated storage image to Image control. I got this helper method from the link "Binding Image stored in the Isolated Storage to Image Control in Windows Phone"
public class IsoStoreImageSource : DependencyObject
{
public static void SetIsoStoreFileName(UIElement element, string value)
{
element.SetValue(IsoStoreFileNameProperty, value);
}
public static string GetIsoStoreFileName(UIElement element)
{
return (string)element.GetValue(IsoStoreFileNameProperty);
}
// Using a DependencyProperty as the backing store for IsoStoreFileName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsoStoreFileNameProperty =
DependencyProperty.RegisterAttached("IsoStoreFileName", typeof(string), typeof(IsoStoreImageSource), new PropertyMetadata("", Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Image img = d as Image;
if (img != null)
{
var path = e.NewValue as string;
SynchronizationContext uiThread = SynchronizationContext.Current;
Task.Factory.StartNew(() =>
{
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(path))
{
var stream = isoStore.OpenFile(path, System.IO.FileMode.Open, FileAccess.Read);
uiThread.Post(_ =>
{
var _img = new BitmapImage();
_img.SetSource(stream);
img.Source = _img;
}, null);
}
}
});
}
}
}
I am using this inside a ListBox control. And if try with default library images everything will work as expected. But if I try with the images with large size( taken through device camera ) the app crashes.
And here is the exception what I am getting
An exception of type 'System.OutOfMemoryException' occurred in System.Windows.ni.dll but was not handled in user code
stack trace
at MS.Internal.FrameworkCallbacks.NotifyManagedDebuggerOnNativeOOM()
at MS.Internal.XcpImports.BitmapSource_SetSource(BitmapSource bitmapSource, CValue& byteStream)
at System.Windows.Media.Imaging.BitmapSource.SetSourceInternal(Stream streamSource)
at System.Windows.Media.Imaging.BitmapImage.SetSourceInternal(Stream streamSource)
at System.Windows.Media.Imaging.BitmapSource.SetSource(Stream streamSource)
at MyaPP.Common.IsoStoreImageSource.<>c__DisplayClass4.<>c__DisplayClass6.b__1(Object _)
The caching within the ListBox might be taking up your memory and this is especially noticeable with larger images. I'm not familiar with the helper method you've posted but try adding this.
if (img != null)
{
BitmapImage bitmapImage = img.Source as BitmapImage;
bitmapImage.UriSource = null;
img.Source = null;
//rest of the code ...
}
Okay, it took some time for me to return to this issue. I'll share my findings here, but I'm not considering them a real answer to the issue, but rather a workaround. However, I hope it will help somebody.
First I want to confirm OutOfMemoryException happens in certain circumstances. But, surprisingly, it depends on the page layout you're using. In fact, if your layout involves StackPanel, you'll have an exception. I guess, it comes down to the fact how MeasureOverride and ArrangeOverride methods are implemented in StackPanel (though I may be completely wrong here). It looks like when ListBox is a child to StackPanel, it tries to load all of the images before display. This, of course, causes the memory leak.
On the other hand, if you use something like Grid as a parent for list of images, then there's no such exceptions, and the memory load is reasonable.
Here's page layout that worked for me:
<Grid>
<ListBox ItemsSource="{Binding IsoStorePics}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image local:IsoStoreImageSource.IsoStoreFileName="{Binding Path}" Margin="5"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
This is the best answer I have for you now. Please let me know if it helped.
You can try like this, Stream object will automatically disposed.
using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication())
{
if (iso.FileExists(imagePath))
{
using (Stream imagestream = new IsolatedStorageFileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read, iso))
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(imagestream);
imgControl.Source = bmp;
}
}
}

Binding Image stored in the Isolated Storage to Image Control in Windows Phone

Is it possible to bind the image present in the Isolates storage to image control through xaml. I found some implementations like getting the image through the property and binding that into xaml control. But this is not the implementation what I am searching for. My question is like, writing an attach property and helper method to fetch the content from Isolated storage. I found a similar implementation in LowProfileImage class, used in windows phone 7. But I think it is deprecated now. If anyone tried similar implementations please help me to achieve the same. Also if implementation have any performance drains please mention that info too.
Yes, it is possible to use images from isolated storage in the app UI. It requires loading the image from the file into the BitmapImage and then binding ImageSource of your control to that BitmapImage. I'm using the following approach:
First, there's a method to load image asynchronously:
private Task<Stream> LoadImageAsync(string filename)
{
return Task.Factory.StartNew<Stream>(() =>
{
if (filename == null)
{
throw new ArgumentException("one of parameters is null");
}
Stream stream = null;
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(filename))
{
stream = isoStore.OpenFile(filename, System.IO.FileMode.Open, FileAccess.Read);
}
}
return stream;
});
}
Then it can be used like this:
public async Task<BitmapSource> FetchImage()
{
BitmapImage image = null;
using (var imageStream = await LoadImageAsync(doc.ImagePath))
{
if (imageStream != null)
{
image = new BitmapImage();
image.SetSource(imageStream);
}
}
return image;
}
And finally you just assign return value of FetchImage() method to some of your view model's property, to which the UI element is bound. Of course, your view model should properly implement INotifyPropertyChanged interface for this approach to work reliably.
If you want to use attached properties approach, here's how you do it:
public class IsoStoreImageSource : DependencyObject
{
public static void SetIsoStoreFileName(UIElement element, string value)
{
element.SetValue(IsoStoreFileNameProperty, value);
}
public static string GetIsoStoreFileName(UIElement element)
{
return (string)element.GetValue(IsoStoreFileNameProperty);
}
// Using a DependencyProperty as the backing store for IsoStoreFileName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsoStoreFileNameProperty =
DependencyProperty.RegisterAttached("IsoStoreFileName", typeof(string), typeof(IsoStoreImageSource), new PropertyMetadata("", Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Image img = d as Image;
if (img != null)
{
var path = e.NewValue as string;
SynchronizationContext uiThread = SynchronizationContext.Current;
Task.Factory.StartNew(() =>
{
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(path))
{
var stream = isoStore.OpenFile(path, System.IO.FileMode.Open, FileAccess.Read);
uiThread.Post(_ =>
{
var _img = new BitmapImage();
_img.SetSource(stream);
img.Source = _img;
}, null);
}
}
});
}
}
}
And then in XAML:
<Image local:IsoStoreImageSource.IsoStoreFileName="{Binding Path}" />
Some limitations of this approach:
It only works on Image control, though you can change this to a whichever type you want. It's just not very generic.
Performance-wise, it will use a thread from the threadpool every time image source is changed. It's the only way to do asynchronous read from isolated storage on Windows Phone 8 right now. And you definitely don't want to do this synchronously.
But it has one one important advantage:
It works! :)
I like the above approach but there is a simpler more hacky way of doing it if you are interested.
You can go into your xaml and bind the image source to an string property then put the file path into the property dynamically.
<!-- XAML CODE -->
<Image Source="{Binding imagePath}"/>
//Behind property
public String imagePath { get; set; }
load your path into the image path then bind the image source to the image path string. You might have to do an INotifyPropertyChanged but this method should work with proper binding.

Resources