[英]How to Load Image Thumbnails to View from IsolatedStorage
目前,我的應用程序的IsolatedStorage中有數百個映像(可能會變成數千個),並且加載時間非常慢。 通常,我的應用程序凍結並失敗。 允許用戶使用CameraCaptureTask拍照,然后將每張圖片保存到IsolatedStorage中,並使用LongListSelector在視圖中顯示。 我嘗試過使用ViewBox並將LongListSelector的GridCellSize設置為較小的大小(最大寬度或高度取決於108的縱橫比),但這在減少從IsolatedStorage加載時的時間或實際圖像大小沒有幫助。 我想知道是否有一種通過從IsolatedStorage加載圖像的縮略圖大小版本來填充視圖的方法來加快加載/渲染時間? 然后,一旦選擇了視圖中的圖像,我將僅從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
是從IsolatedStorage加載圖像的地方,將圖像尺寸減小到最大寬度或最大高度108以減少加載/渲染時間的最佳,最有效的方法是什么? 這是首先解決此問題的最佳方法嗎? 任何幫助,建議或想法將不勝感激。
編輯:添加了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());
}
如果我不得不通過查看代碼來猜測,那么您的問題是,當其容器滾動到視圖中時,您不允許電話框架按需加載圖像。 您的代碼將在頁面加載時立即解碼這些文件中的每個文件,而不是在它們可見時對其進行解碼。 您還將使用不推薦使用的隔離存儲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屬性,因為它是有效的URI,可以作為Source綁定提供給圖像控件。
- 編輯 -
我添加了代碼,這些代碼演示了更改的加載過程(如果沒有其他說明)。 我已為此回購連接了INotifyPropertyChanged
接口,以便UI會收到有關Pictures集合更改的通知,並將setter更改為private(這是我對您所需行為的假設)。
對於讀寫操作,您可以在System.IO名稱空間中簽出StorageFile類的擴展名。 只需將其導入using指令中,您就可以訪問以下內容:
save方法的實現可能要復雜得多,具體取決於您接受的save目錄參數。 如果允許指定具有多個子目錄的完整路徑的目錄參數,則將大大改變代碼,從僅允許保存到第一級子目錄開始。 您必須檢查每個子文件夾是否存在,如果沒有,請創建它。 否則,下面用於訪問“圖片”文件夾的代碼示例就足以演示如何獲取目標文件夾。
示例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());
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.