フォトブラウザWPFアプリ-パフォーマンスの向上

Aug 18 2020

私は個人的なプロジェクトとしてWPFフォトブロワーアプリに取り組んでいます。毎回画像全体を読むと、サムネイル処理の現在の戦略が不十分であることがわかります。これは、画像が数個以上あるフォルダを参照しているときに表示されます。誰かがコードのこの側面、または他の何かを改善するかもしれない変更を提案することができれば、それは非常に役に立ちます。

コードのスリム化されたバージョンを以下に示します。これはフォルダを取り、その中の画像のサムネイルと名前を表示します。(フルアプリには、画像に対してさまざまな操作を実行するためのボタンもあります。)

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

回答

1 RickDavin Aug 19 2020 at 20:23

あなたの不動産セッターの何人かはやりすぎだと思います。たとえば、のMainWindow場合、Folderプロパティセッターはフォルダー名を設定するだけでなく、そのフォルダー内のすべてのファイルを列挙して収集するのにも時間がかかります。これは、単一責任の原則およびおそらく驚き最小の原則に反します。私はそのようなアプリケーションを使用しましたが、フォルダーに移動するだけで、クリックするたびにフォルダーからの読み取りが強制されるように見えることに不満を感じています。つまり、フォルダの設定はフォルダの設定のみであり、そのフォルダ内のファイルの列挙は別のアクションである必要があります。

パフォーマンスは、いくつかの非同期呼び出しを実装し、何かをストリーミングし続けることで恩恵を受ける可能性があります。でストリーミングしEnumerateFilesますが、リストに追加します。これにより、パフォーマンスが低下する可能性があります。私はあなたがファイルを読む必要があると思う正確な瞬間を調査し、その瞬間までそれらを読むのを遅らせます。

に関連付けられてBitMapいる非同期メソッドについてよく読んでください。この投稿では、ビットマップをC#と非同期でロードする方法を見つけました。また、バックグラウンドタスクを使用して、ファイルを列挙したり、サムネイルを読み込んだりすることを検討してください。