0

I currently have several hundred images in IsolatedStorage of my application (which may turn to thousands), and the loading time is rediculously slow. Often times my application freezes and fails. The user is allowed to use the CameraCaptureTask to take pictures, and then each picture is saved to IsolatedStorage as well as shown in the View using a LongListSelector. I've tried using a ViewBox and setting the GridCellSize of the LongListSelector to a small size (max width or height depending on aspect ratio of 108), but this does not help in reducing the time or actual image size when loading from IsolatedStorage. I was wondering if there was a quick way to speed up the loading/rendering time by loading a thumbnail sized version of an image from IsolatedStorage to populate the view? Then once an image in the view is selected, I will pull only that image from IsolatedStorage.

MainPage.xaml

<phone:PhoneApplicationPage.Resources>

<Style x:Key="PhoneButtonBase" TargetType="ButtonBase">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
    <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
    <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
    <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
    <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
    <Setter Property="Padding" Value="10,5,10,6"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ButtonBase">
                <Grid Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver"/>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneButtonBasePressedForegroundBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneAccentBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonBackground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
                        <ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="PhoneRadioButtonCheckBoxBase" BasedOn="{StaticResource PhoneButtonBase}" TargetType="ToggleButton">
    <Setter Property="Background" Value="{StaticResource PhoneRadioCheckBoxBrush}"/>
    <Setter Property="BorderBrush" Value="{StaticResource PhoneRadioCheckBoxBorderBrush}"/>
    <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
    <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="0"/>
</Style>
<Style x:Key="RadioButtonStyle1" BasedOn="{StaticResource PhoneRadioButtonCheckBoxBase}" TargetType="RadioButton">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="RadioButton">
                <Grid Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver"/>
                            <VisualState x:Name="Pressed"/>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked"/>
                            <VisualState x:Name="Unchecked"/>
                            <VisualState x:Name="Indeterminate"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

..

            <phone:LongListSelector.ItemTemplate>
                <DataTemplate>
                    <ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
                        <ContentControl.Resources>
                            <Storyboard x:Name="CheckedStoryboard">
                                <ColorAnimation Duration="0" To="#FF1BA1E2" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brd" d:IsOptimized="True"/>
                            </Storyboard>
                        </ContentControl.Resources>
                        <RadioButton x:Name="radioButton" HorizontalAlignment="Stretch" Margin="0,0,0,0" GroupName="A" Background="Black" Style="{StaticResource RadioButtonStyle1}" >
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="Click">
                                    <eim:ControlStoryboardAction Storyboard="{StaticResource CheckedStoryboard}"/>
                                </i:EventTrigger>
                                <i:EventTrigger EventName="Unchecked">
                                    <eim:ControlStoryboardAction ControlStoryboardOption="Stop" Storyboard="{StaticResource CheckedStoryboard}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                            <Border x:Name="MyBorder" Background="Transparent">
                                <Border x:Name="brd" CornerRadius="10" Width="Auto" BorderThickness="3" BorderBrush="Transparent">
                                    <toolkit:ContextMenuService.ContextMenu>
                                        <toolkit:ContextMenu x:Name="imgListContextMenu" Background="{StaticResource PhoneChromeBrush}">                                                                                                        <toolkit:MenuItem Foreground="{StaticResource PhoneForegroundBrush}" Header="{Binding Path=LocalizedResources.MainPage_ContextMenu_Delete, Source={StaticResource LocalizedStrings}}" Click="deleteContextMenuItem_Click"/>
                                        </toolkit:ContextMenu>
                                    </toolkit:ContextMenuService.ContextMenu>
                                    <Viewbox Width="108" Height="108">
                                        <Image x:Name="recentImage" Source="{Binding Source}" Margin="6,6" Width="108"/>
                                    </Viewbox>
                                </Border>
                            </Border>
                        </RadioButton>
                    </ContentControl>
                </DataTemplate>
            </phone:LongListSelector.ItemTemplate>

        </phone:LongListSelector>

MainPage.xaml.cs

protected override void OnNavigatedTo(NavigationEventArgs e)
{   
    if (Settings.AscendingSort.Value)
    {
        App.PictureList.Pictures = new ObservableCollection<Models.Picture>(App.PictureList.Pictures.OrderBy(x => x.DateTaken)); 
        Recent.ItemsSource = App.PictureList.Pictures;
    }
    else
    {
        App.PictureList.Pictures = new ObservableCollection<Models.Picture>(App.PictureList.Pictures.OrderByDescending(x => x.DateTaken));
        Recent.ItemsSource = App.PictureList.Pictures;
    }    
}

...

private void cameraTask_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        var capturedPicture = new CapturedPicture(e.OriginalFileName, stream);
    }
}

private void recent_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var item = (sender as LongListSelector).SelectedItem;
    if (item == null)
        return;

    capturedPicture = null; 
    //Get picture
    capturedPicture = (sender as LongListSelector).SelectedItem as CapturedPicture;

    if (capturedPicture != null)
    {                
        fileName = capturedPicture.FileName;
    }
}

App.xaml.cs

public static PictureRepository PictureList
{
    get
    {
        return PictureRepository.Instance;
    }
}

PictureRepository.cs

#region Constants

public const string IsolatedStoragePath = "Pictures";

#endregion

#region Fields

private ObservableCollection<Picture> _pictures = new ObservableCollection<Picture>();

#endregion

#region Properties

public ObservableCollection<Picture> Pictures
{
    get { return _pictures; }
    set{ pictures = value; }
}

#endregion

#region Singleton Pattern

private PictureRepository()
{
    LoadAllPicturesFromIsolatedStorage();
}

public static readonly PictureRepository Instance = new PictureRepository();

#endregion

/// <summary>        
/// Saves to local storage
/// This method gets two parameters: the captured picture instance and the name of the pictures folder in the isolated storage
/// </summary>
/// <param name="capturedPicture"></param>
/// <param name="directory"></param>
public void SaveToLocalStorage(CapturedPicture capturedPicture, string directory)
{
    //call IsolatedStorageFile.GetUserStoreForApplication to get an isolated storage file
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
    //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists.
    isoFile.EnsureDirectory(directory);

    //Combine the pictures folder and captured picture file name and use this path to create a new file 
    string filePath = Path.Combine(directory, capturedPicture.FileName);
    using (var fileStream = isoFile.CreateFile(filePath))
    {
        using (var writer = new BinaryWriter(fileStream))
        {
            capturedPicture.Serialize(writer);
        }
    }
}

/// <summary>
/// To load all saved pictures and add them to the pictures list page
/// </summary>
public CapturedPicture LoadFromLocalStorage(string fileName, string directory)
{
    //To open the file, add a call to the IsolatedStorageFile.GetUserStoreForApplication
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();

    //Combine the directory and file name
    string filePath = Path.Combine(directory, fileName);
    //use the path to open the picture file from the isolated storage by using the IsolatedStorageFile.OpenFile method
    using (var fileStream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
    {
        //create a BinaryReader instance for deserializing the CapturedPicture instance
        using (var reader = new BinaryReader(fileStream))
        {
            var capturedPicture = new CapturedPicture();
            //create a new instance of the type CapturedPicture called CapturedPicture.Deserialize to deserialize the captured picture and return it
            capturedPicture.Deserialize(reader);
            return capturedPicture;
        }
    }
}

/// <summary>
/// To load all the pictures at start time
/// </summary>
private void LoadAllPicturesFromIsolatedStorage()
{
    //add call to the IsolatedStorageFile.GetUserStoreForApplication to open an isolated storage file
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
    //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists
    isoFile.EnsureDirectory(IsolatedStoragePath);

    //Call the IsolatedStorageFile.GetFileNames using the pictures directory and *.jpg as a filter to get all saved pictures
    var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, "*.jpg"));
    //var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, ""));

    //Iterate through all the picture files in the list and load each using the LoadFromLocalStorage you created earlier
    foreach (var pictureFile in pictureFiles)
    {
        var picture = LoadFromLocalStorage(pictureFile, IsolatedStoragePath);
        _pictures.Add(picture);
    }
}

LoadAllPicturesFromIsolatedStorage is where the images from IsolatedStorage are being loaded, what would be the best and most efficient way to reduce the image size to a max width or height of 108 to decrease loading/rendering time? And is this the best way to go about this in the first place? Any help, suggestions, or thoughts would be greatly appreciated.

EDIT: added CapturedPicture.cs, Picture.cs

CapturedPicture.cs

[DataContract]
public class CapturedPicture : Picture
{
    [DataMember]
    public byte[] ImageBytes
    {
        get;
        set;
    }

    [DataMember]
    public string FileName
    {
        get;
        set;
    }

    protected override BitmapSource CreateBitmapSource()
    {
        BitmapSource source = null;
        if (ImageBytes != null)
        {
            using (var stream = new MemoryStream(ImageBytes))
            {
                source = PictureDecoder.DecodeJpeg(stream);
                //source = PictureDecoder.DecodeJpeg(stream, 500, 500);
            }
        }
        return source;
    }

    public CapturedPicture()
    {
    }

    public CapturedPicture(string capturedFileName, Stream capturedImageStream)
    {
        ImageBytes = ReadImageBytes(capturedImageStream);
        //DateTaken = DateTime.Now.ToLongDateString();
        //DateTaken = DateTime.Now.ToString();
        //DateTaken = DateTime.Now.ToString("o");
        DateTaken = DateTime.UtcNow;
        FileName = System.IO.Path.GetFileName(capturedFileName);
    }

    private byte[] ReadImageBytes(Stream imageStream)
    {
        byte[] imageBytes = new byte[imageStream.Length];
        imageStream.Read(imageBytes, 0, imageBytes.Length);
        return imageBytes;
    }

    public override void Serialize(BinaryWriter writer)
    {
        base.Serialize(writer);
        writer.Write(ImageBytes.Length);
        writer.Write(ImageBytes);
        writer.Write(FileName);//writer.WriteString(FileName);
    }

    public override void Deserialize(BinaryReader reader)
    {
        base.Deserialize(reader);
        int bytesCount = reader.ReadInt32();
        ImageBytes = reader.ReadBytes(bytesCount);
        FileName = reader.ReadString();
    }

Picture.cs

[DataMember]
public string Address
{
    get { return GetValue(() => Address); }
    set { SetValue(() => Address, value); }
}

[DataMember]
public string Note
{
    get { return GetValue(() => Note); }
    set { SetValue(() => Note, value); }
}

[DataMember]
public DateTime DateTaken
{
    get { return GetValue(() => DateTaken); }
    set { SetValue(() => DateTaken, value); }
}

[IgnoreDataMember]
public BitmapSource Source
{
    get
    {
        return CreateBitmapSource();
    }
}

protected abstract BitmapSource CreateBitmapSource();

//In the Serialize method, store the Position, Address, Note, and DateTaken properties
public virtual void Serialize(BinaryWriter writer)
{            
    writer.Write(DateTaken.ToString()); //writer.WriteString(DateTaken);
}

//In the Deserialize method, read the data in the same order you’ve written it
public virtual void Deserialize(BinaryReader reader)
{
    DateTaken = DateTime.Parse(reader.ReadString());
}
Matthew
  • 3,976
  • 15
  • 66
  • 130
  • 1
    Ideally, I'd think it would be best to resize the images when they get saved so that you don't need to resize them every time you have to present them to the user. If the images are used elsewhere, this may mean saving two separate copies, the original and the resized version. – Anthony Sep 23 '13 at 18:03
  • Thanks for the tip. I have in fact tried that, yesterday I posted another question regarding that issue on here http://stackoverflow.com/questions/18951955/how-to-change-size-of-image but am having trouble solving it. Also, due to the fact that there could be thousands of images, I thought it prudent to reduce images to thumbnail size for loading to the view as well. – Matthew Sep 23 '13 at 18:14
  • Is `CapturedPicture` a custom class, I'm not seeing it in the framework (I've never worked with windows-phone-8). – Anthony Sep 23 '13 at 18:23

1 Answers1

0

If I had to guess from looking at the code, your problem is that you are not allowing the phone framework to load the images on demand when their container is scrolled into view. Your code is going to decode every one of those files immediately on page load rather than as they become visible. You're also using the deprecated isolated storage API instead of the new asynchronous Windows.Storage namespace, which is likely going to block your UI thread.

You can refactor this however you'd prefer, but consider something similar to the following:

Code Behind

public partial class MyPage : PhoneApplicationPage
{
    public MyPage()
    {
        InitializeComponent();

        this.Loaded += async (sender, e) =>
        {
            var folder = await ApplicationData.Current.LocalFolder.GetFolderAsync("Pictures");
            var images = await folder.GetFilesAsync();
            Recent.ItemsSource = images.ToList();
        };
    }
}

XAML

<phone:LongListSelector x:Name="Recent" Margin="0,0,0,72" LayoutMode="Grid" GridCellSize="108,108">
    <phone:LongListSelector.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding Path}" Margin="6" Stretch="UniformToFill" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </DataTemplate>
    </phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>

This is an overly simplistic example, but the upshot is that you can build a list of StorageFile and bind your LongListSelector.ItemSource property to it. The Image.Source dependency property is pretty malleable, so you can pass it the image file's path string directly and it will do the heavy lifting of creating an image source object for you, scaled and cropped to the specifications of the image container. This process only occurs as the image is scrolled into view so you'll only ever have a handful of images loaded at a time. The LongListSelector will do the work of rendering the item templates on demand for you as the user scrolls the list, generating the new Image controls and its Source at that time.

If for whatever reason you feel the need to transform these records to your CapturedPicture class, feel free. The one most important piece of information you'll need to map is the Path property from these file objects, as it is a valid URI to provide to the image control as the Source binding.

--EDIT--

I've added code that demonstrates, if nothing else, the changed loading process. I've wired up the INotifyPropertyChanged interface for this repo so that the UI will be notified of changes to the Pictures collection and changed the setter to private (this is an assumption on my part about your desired behavior).

For read and write operations, you can check out extensions for the StorageFile class in the System.IO namespace. Just import it in a using directive and you'll have access to the following:

Implementation of the save method may be much more complex depending on what you accept as a parameter for the save directory. If you allow for specifying a directory parameter with a full path of multiple subdirectories, that would change the code substantially from just allowing saving to a first-level subdirectory. You'd have to check for the existence of each subfolder and create it if not. Otherwise, the code sample below for accessing the Pictures folder should suffice to demonstrate how to obtain the target folder.

Sample PictureRepository.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Search;

namespace MyApp.Media
{
    public class PictureRepository : INotifyPropertyChanged
    {
        #region Constants

        public const string IsolatedStoragePath = "Pictures";

        #endregion

        #region Fields

        private ObservableCollection<StorageFile> _pictures = new ObservableCollection<StorageFile>();

        #endregion

        #region Properties

        public ObservableCollection<StorageFile> Pictures
        {
            get { return _pictures; }
            private set
            {
                RaisePropertyChanged("Pictures");
                _pictures = value;
            }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region Singleton Pattern

        private PictureRepository()
        {
            // This call will warn that execution of the method will continue without waiting on completion
            // This is unimportant because the remainder of the constructor is not dependent on its initialization
            // and the UI will be notified of the change in the collection and respond at that time
            LoadAllPicturesFromIsolatedStorageAsync();
        }

        public static readonly PictureRepository Instance = new PictureRepository();

        #endregion

        /// <summary>
        /// To load all the pictures at start time
        /// </summary>
        private async Task LoadAllPicturesFromIsolatedStorageAsync()
        {
            // Create or open the target folder
            var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(IsolatedStoragePath, CreationCollisionOption.OpenIfExists);

            // Create a query for files with the JPEG extension
            var query = folder.CreateFileQueryWithOptions(new QueryOptions(CommonFileQuery.OrderByName, new string[] { ".jpg" }));

            // Update the Pictures collection, which will raise the PropertyChanged event and cause the UI to bind
            Pictures = new ObservableCollection<StorageFile>(await query.GetFilesAsync());
        }
    }
}
lsuarez
  • 4,952
  • 1
  • 29
  • 51
  • I look forward to trying this out. I updated my original post to include the full implmentation of my `LongListSelector`. Using your suggested solution, will the images be loaded on demand when the LongListSelector is scrolled? Also, instead of `Recent.ItemsSource = images.ToList();` I would like to be able to use my sorting feature to show the list in ascending or descending order. How might I modify this based on my original post (and update) to accomplish this? – Matthew Sep 24 '13 at 15:06
  • One more question, I'm assuming I have to change the entire `PictureRepository.cs` implemenation to use the `Windows.Storage` namespace as opposed to `IsolatedStorage`. Could you suggest some sites to reference or how to accomplish this? I have yet to practice this method. – Matthew Sep 24 '13 at 15:08
  • @Matthew I'll have a look over some of this and get back to you on it. There's a bit to take in. As I said, the approach provided is an overly simplified example, so you'd have to adapt it to suit your specific needs. My only suggestion is that you use the new Storage APIs to retrieve the Path property of the images you're loading and augment your Picture class to include that information so it can be bound in your template. I may have an upcoming edit to give you a more specific example and I'll see what I come across for a Storage API tutorial. – lsuarez Sep 24 '13 at 15:30
  • @Matthew If you need an example of how to use this in your specific case, if you wouldn't mind, what does the `Picture` class look like? I feel like the `CapturedPicture` class may end up having a little less utility than it does now by the time things are said and done. It might not be the worst idea to just return a `StorageFile` instead. – lsuarez Sep 24 '13 at 15:35
  • I just added `Picture.cs` to my original post. And yes I'm sure it will be a learning curve employing the new measures, but definitely worthwhile. I am interested to see how the new Storage APIs will work in my particular case. – Matthew Sep 24 '13 at 16:28
  • @Matthew I've added some sample code and links to my answer to demonstrate use of the Storage API similar to the way you performed the operation in your original implementation. I have, however, opted to use a collection of StorageFiles in this particular sample. You could map data from that class to your Picture or CapturedPicture class if desired, but do not initialize the ImageBytes property, as reading that data before it becomes necessary will slow down page load. The only information relevant to your binding is the Path property. – lsuarez Sep 24 '13 at 16:51
  • This will definitely be interesting to test out. And yes for the saving I am only saving all of the images to a single subdirectory `public const string IsolatedStoragePath = "Pictures";`. How does the new implementation know to only load a few images at a time like you said before? It seems that it loads all images at start time which is a huge constraint from my original post. – Matthew Sep 24 '13 at 17:45
  • I just added my cameraTask_Completed and recent_SelectionChanged events to my original post. From what I can tell, the new implementation loads the images into the LongListSelector without setting them as type `CapturedPicture`, but will assign an item from the LongListSelector to CapturedPicture when it is selected or saved only. How does it know to create an image for each file saved when the loading process occurs. Also, how exactly should I perform the new save operation using this type? – Matthew Sep 24 '13 at 18:34
  • 1
    @Matthew The load method doesn't read the files themselves. It just accesses the directory's file listing so it can obtain the paths to all the photos. That's not the same as a full on disk access of each file. As much as I'd like to continue helping, at some point you need to adapt the technique to your own code base. As I said in my comment, I just chose to create the list as a list of StorageFile. If you want to map the StorageFile class to your CapturedPicture class, you'll need to do that however you see fit (i.e. a LINQ Select call), but do not populate ImageBytes, as I said before. – lsuarez Sep 24 '13 at 19:05
  • Oh yes, I understand, thanks so much for the quick overview of the newest methods, I will certainly be playing around with this this evening. I'm not sure what you mean by not populating ImageBytes, though. Once i obtain all the paths to the photos, don't I have to construct them using CapturedImage (like in `LoadFromLocalStorage` being used in `LoadAllPicturesFromIsolatedStorage` in my original `PictureRepository.cs` to be used in the LongListSelector? – Matthew Sep 24 '13 at 20:27
  • 1
    @Matthew No. You just have to provide the image's file path (given to you by a property of the StorageFile class) to the image's Source DependencyProperty like I did in the original example. The framework will create an actual ImageSource object using the image at that path ONLY when it constructs the DataTemplate for new items being scrolled into view. That means reading of the file is deferred until it's absolutely necessary. None of your code behind should load these files itself. – lsuarez Sep 24 '13 at 20:44