Application WPF du navigateur photo - améliorations des performances
Je travaille sur l'application WPF photo broswer en tant que projet personnel. Je me rends compte que ma stratégie actuelle de gestion des vignettes est médiocre car je lis l'image complète à chaque fois - cela se voit lors de la navigation dans un dossier avec plus de quelques images. Si quelqu'un peut suggérer des changements susceptibles d'améliorer cet aspect du code, ou quoi que ce soit d'autre, ce serait très utile.
Une version allégée du code est ci-dessous. Cela prend un dossier et affiche les vignettes et les noms des images qu'il contient. (L'application complète dispose également de boutons pour effectuer diverses opérations sur les images.)
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));
}
}
}
Réponses
Je pense que certains de vos colocataires en font trop. Par exemple dans MainWindow
, le Folder
paramètre de propriété ne définit pas seulement le nom du dossier, mais il prend également du temps pour énumérer et collecter tous les fichiers de ce dossier. Cela va à l'encontre du principe de responsabilité unique et peut-être du principe du moindre étonnement . J'ai utilisé de telles applications comme ça et je suis frustré de constater que tout ce que je voulais faire était de naviguer vers un dossier, mais chaque clic semble forcer les lectures à partir d'un dossier. En bref, la définition d'un dossier ne doit définir qu'un dossier et l'énumération des fichiers de ce dossier doit être une action distincte.
Les performances pourraient bénéficier de la mise en œuvre de certains appels asynchrones et de garder certains flux en continu. Vous diffusez avec EnumerateFiles
mais vous les ajoutez à une liste. Cela peut avoir un impact négatif sur les performances. J'enquêterais sur l'instant exact où vous pensez avoir besoin de lire les fichiers et je retarderais leur lecture jusqu'à cet instant.
Vous devriez lire toutes les méthodes asynchrones associées à BitMap
. Je trouve ce post comment charger un bitmap de manière asynchrone avec C #. Et vous pouvez envisager d'utiliser une tâche d'arrière-plan pour énumérer les fichiers ou charger des miniatures.