Aplicativo WPF do navegador de fotos - melhorias de desempenho
Estou trabalhando no aplicativo WPF Photo Broswer como um projeto pessoal. Percebo que minha estratégia atual de manuseio de miniaturas é ruim, pois leio a imagem inteira a cada vez - isso aparece ao navegar em uma pasta com mais do que algumas imagens. Se alguém puder sugerir mudanças que possam melhorar este aspecto do código, ou qualquer outra coisa, isso seria muito útil.
Uma versão reduzida do código está abaixo. Isso pega uma pasta e exibe miniaturas e nomes das imagens dentro dela. (O aplicativo completo também possui botões para realizar várias operações nas imagens.)
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
namespace PhotoBrowser
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private string _folder;
public string Folder
{
get => _folder;
set
{
_folder = value;
if (Directory.Exists(_folder))
{
var filesInFolder = Directory.EnumerateFiles(_folder);
var files_ = new ObservableCollection<FileItem>();
foreach (string file in filesInFolder)
{
files_.Add(new FileItem(file, false));
}
Files = files_;
}
else
{
Files = new ObservableCollection<FileItem>();
}
OnPropertyChanged();
}
}
private ObservableCollection<FileItem> _files;
public ObservableCollection<FileItem> Files
{
get => _files;
set
{
_files = value;
OnPropertyChanged();
}
}
public MainWindowViewModel() { }
}
}
MainWindow.xaml
<Window x:Class="PhotoBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PhotoBrowser"
mc:Ignorable="d"
Title="Photo Browser" Height="800" Width="800" MinHeight="300" MinWidth="400">
<Grid HorizontalAlignment="Stretch" Height="Auto" Margin="10,10,10,10" VerticalAlignment="Stretch" Width="Auto" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Folder"
Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<TextBox x:Name="Folder"
Text="{Binding Folder, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
HorizontalAlignment="Stretch" Height="25" TextWrapping="NoWrap" Margin="5,5,5,5"
VerticalAlignment="Center"/>
<ListBox x:Name="FilesInCurrentFolder"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch"
BorderBrush="Black" Height="Auto" Width="772" SelectionMode="Extended"
ItemsSource="{Binding Files, UpdateSourceTrigger=PropertyChanged}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Thumbnail}" Width="100" Height="100"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</Window>
FileItem.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows.Media.Imaging;
namespace PhotoBrowser
{
public class FileItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private string _fileName;
public string FileName
{
get => _fileName;
set
{
_fileName = value;
OnPropertyChanged();
OnPropertyChanged("Name");
}
}
private string _name;
public string Name
{
get => Path.GetFileName(_fileName);
set
{
_name = value;
_fileName = Path.Combine(Path.GetDirectoryName(_fileName), value);
OnPropertyChanged();
OnPropertyChanged("FileName");
}
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
_isSelected = value;
OnPropertyChanged();
}
}
private BitmapSource _thumbnail;
public BitmapSource Thumbnail
{
get => _thumbnail;
set
{
_thumbnail = value;
OnPropertyChanged();
}
}
public FileItem(string fileName)
{
this.FileName = fileName;
GetThumbnail();
}
public FileItem(string fileName, bool isSelected) : this(fileName)
{
this.IsSelected = isSelected;
}
public void GetThumbnail()
{
Image image = new Bitmap(FileName);
image = image.GetThumbnailImage(100, 100, () => false, IntPtr.Zero);
Thumbnail = new BitmapImage(new Uri(FileName));
}
}
}
Respostas
Acho que alguns de seus definidores de propriedades estão fazendo muito. Por exemplo MainWindow
, em , o Folder
configurador de propriedade não apenas define o nome da pasta, mas também leva tempo para enumerar e coletar todos os arquivos dentro dessa pasta. Isso vai contra o Princípio da Responsabilidade Única e talvez o Princípio do Menor Espanto . Tenho usado esses aplicativos como esse e fico frustrado porque tudo o que eu queria fazer era navegar até uma pasta, mas cada clique parece forçar a leitura de uma pasta. Resumindo, definir uma pasta deve apenas definir uma pasta, e enumerar os arquivos nessa pasta deve ser uma ação separada.
O desempenho pode se beneficiar da implementação de algumas chamadas assíncronas e manter algumas coisas em streaming. Você faz stream com, EnumerateFiles
mas os adiciona a uma lista. Isso pode prejudicar o desempenho. Eu investigaria o instante exato em que você acha que precisa ler os arquivos e atrasaria a leitura até esse momento.
Você deve ler sobre quaisquer métodos assíncronos associados a BitMap
. Acho que este post mostra como carregar um bitmap de forma assíncrona com C #. E você pode considerar o uso de uma tarefa em segundo plano para enumerar arquivos ou carregar miniaturas.