Aplicación WPF del navegador de fotos: mejoras de rendimiento

Aug 18 2020

Estoy trabajando en la aplicación WPF Photo Broswer como proyecto personal. Me doy cuenta de que mi estrategia actual para el manejo de miniaturas es deficiente, ya que leo la imagen completa cada vez; esto se muestra cuando navego por una carpeta con más de unas pocas imágenes. Si alguien puede sugerir cambios que puedan mejorar este aspecto del código, o cualquier otra cosa, sería muy útil.

A continuación se muestra una versión reducida del código. Esto toma una carpeta y muestra miniaturas y nombres de las imágenes dentro de ella. (La aplicación completa también tiene botones para realizar varias operaciones en las imágenes).

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));
        }
    }
}

Respuestas

1 RickDavin Aug 19 2020 at 20:23

Creo que algunos de sus establecedores de propiedades están haciendo demasiado. Por ejemplo MainWindow, en , el Folderdefinidor de propiedades no solo establece el nombre de la carpeta, sino que también lleva tiempo enumerar y recopilar todos los archivos dentro de esa carpeta. Esto va en contra del principio de responsabilidad única y quizás el principio de menor asombro . He usado aplicaciones como esa y me frustra que cuando todo lo que quería hacer era navegar a una carpeta, cada clic parece forzar la lectura de una carpeta. En resumen, configurar una carpeta solo debe establecer una carpeta, y enumerar los archivos en esa carpeta debe ser una acción separada.

El rendimiento podría beneficiarse de la implementación de algunas llamadas asíncronas y mantener la transmisión de algunas cosas. Transmite con EnumerateFilespero los agrega a una lista. Esto puede tener un impacto en el rendimiento. Investigaría el instante exacto en que crees que necesitas leer archivos y retrasaría su lectura hasta ese instante.

Debería leer sobre cualquier método asincrónico asociado con BitMap. Encuentro esta publicación sobre cómo cargar un mapa de bits de forma asincrónica con C #. Y podría considerar usar una Tarea en segundo plano para enumerar archivos o cargar miniaturas.