DataBinding을 사용하여 매우 느린 ListBox 채우기

PersuitOfPerfection

이전에는 목록 상자에 항목을 수동으로 추가하기 위해 뒤에있는 코드를 사용했지만 매우 느 렸습니다. 성능 측면에서 XAML을 통한 데이터 바인딩이 가장 좋은 방법이라고 들었습니다.

그래서 데이터 바인딩이 작동하도록 할 수 있었지만 (바인딩에 처음 사용) 실망스럽게도 이전의 비 데이터 바인딩 방법보다 성능이 더 좋지 않습니다.

아이디어는 내 ListBox에 그 아래에 이름이있는 이미지가 포함되어 있다는 것입니다. 몇 가지 벤치마킹을했고 54 개 항목을 표시하는 데 8 초가 걸립니다. 사용자가 기다리기에는 당연히 너무 깁니다.

소스 이미지는 최대 2100x1535px이고 범위는 파일 당 400kb> 4mb입니다.

이 문제를 재현하는 데 필요한 이미지는 여기에서 찾을 수 있습니다. 질문에 대한 답변으로 제거 된 링크이며 내 서버에 대역폭 허용량이 많지 않습니다. 다른 이미지 소스 : https://imgur.com/a/jmbv6

아래에서 재현 가능한 문제의 예를 만들었습니다. 이걸 너무 느리게 만드는 내가 뭘 잘못하고 있니?

감사합니다.

XAML :

<Window x:Class="WpfApplication1.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:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800" WindowState="Maximized">
    <Grid>
        <ListBox x:Name="listBoxItems" ItemsSource="{Binding ItemsCollection}"
                    ScrollViewer.HorizontalScrollBarVisibility="Disabled">

            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel IsItemsHost="True" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>

            <ListBox.ItemTemplate>
                <DataTemplate>
                    <VirtualizingStackPanel>
                        <Image Width="278" Height="178">
                            <Image.Source>
                                <BitmapImage DecodePixelWidth="278" UriSource="{Binding ImagePath}" CreateOptions="IgnoreColorProfile" />
                            </Image.Source>
                        </Image>
                        <TextBlock Text="{Binding Name}" FontSize="16" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
                    </VirtualizingStackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

뒤에있는 코드 :

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        internal class Item : INotifyPropertyChanged
        {
            public Item(string name = null)
            {
                this.Name = name;
            }

            public string Name { get; set; }
            public string ImagePath { get; set; }

            public event PropertyChangedEventHandler PropertyChanged;
            private void NotifyPropertyChanged(String propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

        ObservableCollection<Item> ItemsCollection;
        List<Item> data;

        public MainWindow()
        {
            InitializeComponent();

            this.data = new List<Item>();
            this.ItemsCollection = new ObservableCollection<Item>();
            this.listBoxItems.ItemsSource = this.ItemsCollection;

            for (int i = 0; i < 49; i ++)
            {
                Item newItem = new Item
                {
                    ImagePath = String.Format(@"Images/{0}.jpg", i + 1),
                    Name = "Item: " + i
                };

                this.data.Add(newItem);
            }

            foreach (var item in this.data.Select((value, i) => new { i, value }))
            {
                Dispatcher.Invoke(new Action(() =>
                {
                    this.ItemsCollection.Add(item.value);
                }), DispatcherPriority.Background);
            }
        }
    }
}
피터 두니 호

이제 사용중인 이미지를 볼 수 있으므로 여기서 주요 문제는 단순히 큰 이미지를로드하는 데 드는 기본 비용이라는 것을 확인할 수 있습니다. 그 이미지 파일을 사용하여 그 시간을 개선 할 방법은 없습니다.

할 수있는 일은 이미지를 비동기식으로로드하여 사용자가 모든 이미지가로드 될 때까지 대기하는 동안 프로그램의 나머지 부분이 반응하도록하거나 이미지 크기를 줄여서 더 빨리로드되도록하는 것입니다. 가능하다면 후자를 강력히 추천합니다.

어떤 이유로 이미지를 배포하고 원래의 큰 크기 형식으로로드해야하는 경우 최소한 비동기 적으로로드해야합니다. 이를 수행하는 방법에는 여러 가지가 있습니다.

가장 간단한 방법은 바인딩 을 설정 Binding.IsAsync하는 것입니다 Image.Source.

<ListBox.ItemTemplate>
  <DataTemplate>
    <StackPanel>
      <Image Width="278" Height="178" Source="{Binding ImagePath, IsAsync=True}"/>
      <TextBlock Text="{Binding Name}" FontSize="16"
                 VerticalAlignment="Bottom" HorizontalAlignment="Center" />
    </StackPanel>
  </DataTemplate>
</ListBox.ItemTemplate>

이 방법의 가장 큰 단점은이 방법을 사용할 DecoderPixelWidth설정할 수 없다는 것입니다 . Image제어는 당신을위한 실제 비트 맵의 경로에서 변환을 처리하고, 다양한 옵션을 설정할 수있는 메커니즘이 없습니다.

기술의 단순함을 감안할 때 적어도 나에게는 이것이 선호되는 방법이라고 생각합니다. 사용자는 프로그램이 반응하고 진행 징후를 보이는 한 모든 데이터를 완전히 초기화하는 데 걸리는 총 시간에 신경 쓰지 않는 경우가 많습니다. 그러나이 DecoderPixelWidth시나리오에서 설정하지 않으면 모든 이미지를로드하는 데 거의 두 배의 시간이 걸렸습니다 (약 7.5 초 대 거의 14 초). 따라서 이미지를 직접 비동기식으로로드하는 데 관심이있을 수 있습니다.

이렇게하려면 이미 익숙한 일반적인 비동기 프로그래밍 기술이 필요합니다. 주된 "문제"는 기본적으로 WPF 비트 맵 처리 클래스가 실제로 필요할 때까지 비트 맵의 ​​실제로드를 연기한다는 것입니다. 데이터를 즉시로드 할 수있는 경우가 아니면 비트 맵을 비동기 적으로 생성하는 것은 도움이되지 않습니다.

다행히도 가능합니다. 단순히 CacheOption속성을 BitmapCacheOption.OnLoad.

원본 예제를 정리하고 적절한 뷰 모델 데이터 구조를 만들고 이미지의 비동기로드를 구현할 자유를 얻었습니다. 이런 식으로로드 시간이 8 초 미만이지만로드가 진행되는 동안 UI는 계속 응답합니다. 저는 두 개의 타이머를 포함 시켰습니다. 하나는 프로그램이 시작된 이후의 경과 시간을 표시하고 주로 UI의 응답 성을 설명하기 위해 있고 다른 하나는 비트 맵 이미지를 실제로로드하는 데 소요 된 시간을 보여줍니다.

XAML :

<Window x:Class="TestSO42639506PopulateListBoxImages.MainWindow"
        x:ClassModifier="internal"
        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:l="clr-namespace:TestSO42639506PopulateListBoxImages"
        mc:Ignorable="d"
        WindowState="Maximized"
        Title="MainWindow" Height="350" Width="525">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel>
      <TextBlock Text="{Binding TotalSeconds, StringFormat=Total seconds: {0:0}}"/>
      <TextBlock Text="{Binding LoadSeconds, StringFormat=Load seconds: {0:0.000}}"/>
    </StackPanel>

    <ListBox x:Name="listBoxItems" ItemsSource="{Binding Data}"
             Grid.Row="1"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled">

      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel IsItemsHost="True" />
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>

      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel>
            <Image Width="278" Height="178" Source="{Binding Bitmap}"/>
            <TextBlock Text="{Binding Name}" FontSize="16"
                       VerticalAlignment="Bottom" HorizontalAlignment="Center" />
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

씨#:

class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void _UpdatePropertyField<T>(
        ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
        {
            return;
        }

        field = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

class Item : NotifyPropertyChangedBase
{
    private string _name;
    private string _imagePath;
    private BitmapSource _bitmap;

    public string Name
    {
        get { return _name; }
        set { _UpdatePropertyField(ref _name, value); }
    }

    public string ImagePath
    {
        get { return _imagePath; }
        set { _UpdatePropertyField(ref _imagePath, value); }
    }

    public BitmapSource Bitmap
    {
        get { return _bitmap; }
        set { _UpdatePropertyField(ref _bitmap, value); }
    }
}

class MainWindowModel : NotifyPropertyChangedBase
{
    public MainWindowModel()
    {
        _RunTimer();
    }

    private async void _RunTimer()
    {
        Stopwatch sw = Stopwatch.StartNew();
        while (true)
        {
            await Task.Delay(1000);
            TotalSeconds = sw.Elapsed.TotalSeconds;
        }
    }

    private ObservableCollection<Item> _data = new ObservableCollection<Item>();
    public ObservableCollection<Item> Data
    {
        get { return _data; }
    }

    private double _totalSeconds;
    public double TotalSeconds
    {
        get { return _totalSeconds; }
        set { _UpdatePropertyField(ref _totalSeconds, value); }
    }

    private double _loadSeconds;
    public double LoadSeconds
    {
        get { return _loadSeconds; }
        set { _UpdatePropertyField(ref _loadSeconds, value); }
    }
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
partial class MainWindow : Window
{
    private readonly MainWindowModel _model = new MainWindowModel();

    public MainWindow()
    {
        DataContext = _model;
        InitializeComponent();

        _LoadItems();
    }

    private async void _LoadItems()
    {
        foreach (Item item in _GetItems())
        {
            _model.Data.Add(item);
        }

        foreach (Item item in _model.Data)
        {
            BitmapSource itemBitmap = await Task.Run(() =>
            {
                Stopwatch sw = Stopwatch.StartNew();
                BitmapImage bitmap = new BitmapImage();

                bitmap.BeginInit();
                // forces immediate load on EndInit() call
                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                bitmap.UriSource = new Uri(item.ImagePath, UriKind.Relative);
                bitmap.DecodePixelWidth = 278;
                bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
                bitmap.EndInit();
                bitmap.Freeze();

                sw.Stop();
                _model.LoadSeconds += sw.Elapsed.TotalSeconds;
                return bitmap;
            });
            item.Bitmap = itemBitmap;
        }
    }

    private static IEnumerable<Item> _GetItems()
    {
        for (int i = 1; i <= 60; i++)
        {
            Item newItem = new Item
            {
                ImagePath = String.Format(@"Images/{0}.jpg", i),
                Name = "Item: " + i
            };

            yield return newItem;
        }
    }
}

방금 .zip에서 내 프로젝트 디렉토리로 파일을 직접 복사했기 때문에 원래 예제와 같이 1-49 대신 1-60과 같은 실제 파일 이름에 해당하도록 이미지 경로 루프를 변경했습니다. 나는 또한 0 기반 레이블을 신경 쓰지 않고 대신 파일 이름과 동일하게 만들었습니다.

여기에 당신의 질문을 직접적으로 다루는 또 다른 질문이 있는지 알아보기 위해 조금 둘러 보았습니다. 정확히 중복 된 것으로 생각되는 것을 찾지 못했지만 WPF를 사용하여 C #에서 BitmapImage를 비동기식으로로드하는 매우 광범위한 기술이 있는데, 위와 유사하거나 동일한 기술을 포함하여 여러 기술을 보여줍니다.

이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.

침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

분류에서Dev

CONNECT BY LEVEL을 사용할 때 매우 느린 쿼리

분류에서Dev

C # asp.net에서 GridView를 사용하여 느린 데이터 채우기

분류에서Dev

매우 느린 dnf

분류에서Dev

배열을 사용하여 매개 변수 채우기-초급

분류에서Dev

GROUP BY를 사용하는 MySQL의 매우 느린 하위 쿼리

분류에서Dev

xampp에서 매우 느린 PHP를 사용하여 파일 업로드

분류에서Dev

CursorAdpter를 사용하는 매우 느린 ListView

분류에서Dev

Ubuntu 18.04에서 매우 느린 부팅을 진단 / 수정하는 방법

분류에서Dev

SQL 쿼리가 모든 행을 방문하고 매우 느린 이유

분류에서Dev

매우 느린 부팅

분류에서Dev

containarized shinyproxy 매우 느린

분류에서Dev

매우 느린 C ++ For 루프

분류에서Dev

ssh 매우 느린 연결

분류에서Dev

매우 느린 DNS 조회

분류에서Dev

매우 느린 SMB 전송

분류에서Dev

매우 느린 DNS 조회

분류에서Dev

매우 느린 DNS 조회

분류에서Dev

매우 느린 참조 일치를 기반으로 두 행을 하나로 결합

분류에서Dev

Windows 8 매우 높은 디스크 사용량 및 느린 IO

분류에서Dev

Eclipse의 높은 CPU 사용량 및 매우 느린 성능

분류에서Dev

Eloquent를 사용할 때 매우 느린 쿼리 whereNotIn

분류에서Dev

매우 느린 Codeigniter 웹 사이트를 개선하는 방법

분류에서Dev

250 개의 행을 정렬하고 복사하는 데 매우 느린 코드 실행

분류에서Dev

Eclipse가 CPU의 약 85 %와 3GB RAM을 사용함 : 매우 느린 성능

분류에서Dev

codeigniter 응용 프로그램의 매우 느린 성능 방지

분류에서Dev

이미지가 많고 매우 느린 웹 사이트

분류에서Dev

USB 하드 드라이브에서 매우 느린 ddrescue

분류에서Dev

매우 느린 Windows 10 UI를 해결하는 방법

분류에서Dev

JSON 파일을 사용하여 목록보기 채우기

Related 관련 기사

  1. 1

    CONNECT BY LEVEL을 사용할 때 매우 느린 쿼리

  2. 2

    C # asp.net에서 GridView를 사용하여 느린 데이터 채우기

  3. 3

    매우 느린 dnf

  4. 4

    배열을 사용하여 매개 변수 채우기-초급

  5. 5

    GROUP BY를 사용하는 MySQL의 매우 느린 하위 쿼리

  6. 6

    xampp에서 매우 느린 PHP를 사용하여 파일 업로드

  7. 7

    CursorAdpter를 사용하는 매우 느린 ListView

  8. 8

    Ubuntu 18.04에서 매우 느린 부팅을 진단 / 수정하는 방법

  9. 9

    SQL 쿼리가 모든 행을 방문하고 매우 느린 이유

  10. 10

    매우 느린 부팅

  11. 11

    containarized shinyproxy 매우 느린

  12. 12

    매우 느린 C ++ For 루프

  13. 13

    ssh 매우 느린 연결

  14. 14

    매우 느린 DNS 조회

  15. 15

    매우 느린 SMB 전송

  16. 16

    매우 느린 DNS 조회

  17. 17

    매우 느린 DNS 조회

  18. 18

    매우 느린 참조 일치를 기반으로 두 행을 하나로 결합

  19. 19

    Windows 8 매우 높은 디스크 사용량 및 느린 IO

  20. 20

    Eclipse의 높은 CPU 사용량 및 매우 느린 성능

  21. 21

    Eloquent를 사용할 때 매우 느린 쿼리 whereNotIn

  22. 22

    매우 느린 Codeigniter 웹 사이트를 개선하는 방법

  23. 23

    250 개의 행을 정렬하고 복사하는 데 매우 느린 코드 실행

  24. 24

    Eclipse가 CPU의 약 85 %와 3GB RAM을 사용함 : 매우 느린 성능

  25. 25

    codeigniter 응용 프로그램의 매우 느린 성능 방지

  26. 26

    이미지가 많고 매우 느린 웹 사이트

  27. 27

    USB 하드 드라이브에서 매우 느린 ddrescue

  28. 28

    매우 느린 Windows 10 UI를 해결하는 방법

  29. 29

    JSON 파일을 사용하여 목록보기 채우기

뜨겁다태그

보관