0

我的应用程序的独立存储中目前有数百张图片(可能变成数千张),加载时间非常慢。我的应用程序经常冻结并失败。允许用户使用 CameraCaptureTask 拍照,然后将每张图片保存到 IsolatedStorage 中,并使用 LongListSelector 显示在 View 中。我尝试使用 ViewBox 并将 LongListSelector 的 GridCellSize 设置为小尺寸(最大宽度或高度取决于 108 的纵横比),但这无助于减少从隔离存储加载时的时间或实际图像大小。我想知道是否有一种快速的方法可以通过从 IsolatedStorage 加载图像的缩略图大小版本来填充视图来加快加载/渲染时间?然后一旦选择了视图中的图像,

主页.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;
    }
}

应用程序.xaml.cs

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

图片库.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是从 IsolatedStorage 加载图像的位置,将图像大小减小到最大宽度或高度 108 以减少加载/渲染时间的最佳和最有效的方法是什么?这是首先解决这个问题的最佳方式吗?任何帮助、建议或想法将不胜感激。

编辑:添加 CapturedPicture.cs、Picture.cs

捕获的图片.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();
    }

图片.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());
}
4

1 回答 1

0

如果我不得不通过查看代码来猜测,您的问题是您不允许手机框架在容器滚动到视图时按需加载图像。您的代码将在页面加载时立即解码这些文件中的每一个,而不是在它们变得可见时解码。您还使用了已弃用的隔离存储 API 而不是新的异步Windows.Storage命名空间,这可能会阻塞您的 UI 线程。

您可以根据自己的喜好对其进行重构,但请考虑类似于以下内容:

代码背后

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>

这是一个过于简单的示例,但结果是您可以构建一个列表StorageFile并将 LongListSelector.ItemSource 属性绑定到它。Image.Source 依赖属性具有很好的延展性,因此您可以直接将图像文件的路径字符串传递给它,它将为您完成创建图像源对象的繁重工作,并根据图像容器的规格进行缩放和裁剪。此过程仅在图像滚动到视图中时发生,因此您一次只能加载少量图像。当用户滚动列表时,LongListSelector 将根据需要为您呈现项目模板,同时生成新的 Image 控件及其 Source。

如果出于某种原因您觉得需要将这些记录转换为您的 CapturedPicture 类,请随意。您需要映射的最重要的一条信息是来自这些文件对象的 Path 属性,因为它是作为 Source 绑定提供给图像控件的有效 URI。

- 编辑 -

我添加了一些代码来演示更改后的加载过程。我已经为此 repo 连接了INotifyPropertyChanged界面,以便 UI 将收到有关 Pictures 集合更改的通知并将 setter 更改为私有(这是我对您所需行为的假设)。

对于读写操作,您可以检查 System.IO 命名空间中 StorageFile 类的扩展。只需在 using 指令中导入它,您就可以访问以下内容:

根据您接受的保存目录参数的不同,保存方法的实现可能要复杂得多。如果您允许指定具有多个子目录的完整路径的目录参数,那将大大改变代码,从仅允许保存到第一级子目录。您必须检查每个子文件夹是否存在,如果不存在则创建它。否则,下面用于访问 Pictures 文件夹的代码示例应该足以演示如何获取目标文件夹。

示例 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());
        }
    }
}
于 2013-09-23T22:55:06.203 回答