ListBox 项目绑定很慢并且冻结 UI WPF C#

问题描述 投票:0回答:1

电影.cs

public class Movie : INotifyPropertyChanged
{

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


    private string name;
    public string Name { get; set; }

    private int id;
    public int Id { get; set; }

    private string ratingNumber;
    public string RatingNumber { get; set; }

    private BitmapSource _bitmap;
    public BitmapSource Bitmap
    {
        get; set;
    }

    private double rating;
    public double Rating
    {
        get
        {
            return rating;
        }
        set
        {
            rating = value;
            rating = rating / 2;
        }
    }

    private ShowType showType;
    public ShowType ShowType { get; set; }


}

服务.cs

 public static FastObservableCollection<Movie> PopularMovies = new FastObservableCollection<Movie>();
public static async Task GetPopularMoviesAsync(string language, int page)
{
    PopularMovies.Clear();
    var popularMovies = await client.GetMoviePopularListAsync(language, page);
    foreach (var movie in popularMovies.Results)
    {
        Movie mov = new Movie()
        {
            Bitmap = GetImage(client.GetImageUrl("w500", movie.PosterPath).AbsoluteUri,ImageType.Poster),
            Id = movie.Id,
            Name = movie.Title,
            Rating = movie.VoteAverage,
            RatingNumber = movie.VoteAverage.ToString(),
            ShowType = ShowType.Movie,
        };
        if (PopularMovies.Any(x => x.Id == movie.Id))
        {

        }
        else
        {
            PopularMovies.Add(mov);
        }
    }
}

Home.xaml.cs

private async void OnLoad()
 {
     var tasks = new List<Task>();
     if (Service.PopularMovies.Count == 0)
     {
         tasks.Add(Service.GetPopularMoviesAsync("en",1));
     }
     await Task.WhenAll
}
  public static BitmapImage GetImage(string url,ImageType imageType)
  {
      var bitmapImage = new BitmapImage(new Uri(url,UriKind.Absolute));
      bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
      return bitmapImage;
}
<ListBox  ScrollViewer.CanContentScroll="True" MaxHeight="500" 
     VirtualizingPanel.IsVirtualizing="true" 
     VirtualizingPanel.VirtualizationMode="Recycling" ItemsSource="{Binding PopularMovies, Mode=OneWay, IsAsync=True}"  Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" SelectionChanged="MoviesDisplay_OnSelectionChanged" ScrollViewer.ScrollChanged="MoviesDisplay_OnScrollChanged"  ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Disabled" Background="Transparent" x:Name="MoviesDisplay">

       <b:Interaction.Behaviors>
           <local:IgnoreMouseWheelBehavior />
       </b:Interaction.Behaviors>
       <ItemsControl.Resources>
           <Style x:Key="ScaleStyle" TargetType="Image">
               <Style.Triggers>
                   <Trigger Property="IsMouseOver" Value="True">
                       <Setter Property="Grid.ZIndex" Value="1"/>
                       <Setter Property="RenderTransform">
                           <Setter.Value>
                               <ScaleTransform ScaleX="1.1" ScaleY="1.1"/>
                           </Setter.Value>
                       </Setter>
                   </Trigger>
               </Style.Triggers>
           </Style>
       </ItemsControl.Resources>
       <ListBox.ItemContainerStyle>
           <Style TargetType="ListBoxItem">
               <Setter Property="IsSelected" Value="{Binding Content.IsSelected, Mode=TwoWay, RelativeSource={RelativeSource Self}}"/>
               <Setter Property="Template">
                   <Setter.Value>
                       <ControlTemplate TargetType="ListBoxItem">
                           <ContentPresenter/>
                       </ControlTemplate>
                   </Setter.Value>
               </Setter>
           </Style>
       </ListBox.ItemContainerStyle>
       <ListBox.ItemTemplate>
           <DataTemplate>
               <Grid Cursor="Hand" Margin="0 0 20 0" >
                   <Grid.ColumnDefinitions>
                       <ColumnDefinition Width="*"></ColumnDefinition>
                   </Grid.ColumnDefinitions>
                   <Grid.RowDefinitions>
                       <RowDefinition Height="*"></RowDefinition>
                       <RowDefinition Height="0.052*"></RowDefinition>
                       <RowDefinition Height="0.05*"></RowDefinition>
                   </Grid.RowDefinitions>

                   <Viewbox HorizontalAlignment="Left" Grid.Row="0" Grid.Column="0">
                       <Image RenderTransformOrigin="0.5,0.5" Style="{StaticResource ScaleStyle}"  Source="{Binding Bitmap}"></Image>
                   </Viewbox>

                   <Viewbox MaxWidth="200" HorizontalAlignment="Left" Grid.Row="1">
                       <TextBlock   TextWrapping="Wrap" Text="{Binding Name}" Foreground="White"></TextBlock>
                   </Viewbox>

                   <Grid Grid.Row="2" Grid.Column="0">
                       <Grid.ColumnDefinitions>
                           <ColumnDefinition Width="0.3*"></ColumnDefinition>
                           <ColumnDefinition Width="*"></ColumnDefinition>
                       </Grid.ColumnDefinitions>

                       <Viewbox HorizontalAlignment="Left">
                           <materialDesign:RatingBar
   x:Name="ReadOnlyRatingBar"
   IsReadOnly="True"
   Value="{Binding Rating}"></materialDesign:RatingBar>
                       </Viewbox>

                       <Viewbox Margin="10 0 0 0" Grid.Column="1" HorizontalAlignment="Left">
                           <TextBlock Text="{Binding RatingNumber}" Foreground="#767676"></TextBlock>
                       </Viewbox>
                   </Grid>
               </Grid>
           </DataTemplate>
       </ListBox.ItemTemplate>
       <ListBox.ItemsPanel>
           <ItemsPanelTemplate>
               <VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True"  VirtualizingStackPanel.VirtualizationMode="Recycling" IsItemsHost="True" Orientation="Horizontal"  />
           </ItemsPanelTemplate>
       </ListBox.ItemsPanel>
   </ListBox>
public class FastObservableCollection<T> : ObservableCollection<T>
{
    private readonly object locker = new object();

    /// <summary>
    /// This private variable holds the flag to
    /// turn on and off the collection changed notification.
    /// </summary>
    private bool suspendCollectionChangeNotification;

    /// <summary>
    /// Initializes a new instance of the FastObservableCollection class.
    /// </summary>
    public FastObservableCollection()
        : base()
    {
        this.suspendCollectionChangeNotification = false;
    }

    /// <summary>
    /// This event is overriden CollectionChanged event of the observable collection.
    /// </summary>
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    /// <summary>
    /// This method adds the given generic list of items
    /// as a range into current collection by casting them as type T.
    /// It then notifies once after all items are added.
    /// </summary>
    /// <param name="items">The source collection.</param>
    public void AddItems(IList<T> items)
    {
        lock (locker)
        {
            this.SuspendCollectionChangeNotification();
            foreach (var i in items)
            {
                InsertItem(Count, i);
            }
            this.NotifyChanges();
        }
    }

    /// <summary>
    /// Raises collection change event.
    /// </summary>
    public void NotifyChanges()
    {
        this.ResumeCollectionChangeNotification();
        var arg
             = new NotifyCollectionChangedEventArgs
                  (NotifyCollectionChangedAction.Reset);
        this.OnCollectionChanged(arg);
    }

    /// <summary>
    /// This method removes the given generic list of items as a range
    /// into current collection by casting them as type T.
    /// It then notifies once after all items are removed.
    /// </summary>
    /// <param name="items">The source collection.</param>
    public void RemoveItems(IList<T> items)
    {
        lock (locker)
        {
            this.SuspendCollectionChangeNotification();
            foreach (var i in items)
            {
                Remove(i);
            }
            this.NotifyChanges();
        }
    }

    /// <summary>
    /// Resumes collection changed notification.
    /// </summary>
    public void ResumeCollectionChangeNotification()
    {
        this.suspendCollectionChangeNotification = false;
    }

    /// <summary>
    /// Suspends collection changed notification.
    /// </summary>
    public void SuspendCollectionChangeNotification()
    {
        this.suspendCollectionChangeNotification = true;
    }

    /// <summary>
    /// This collection changed event performs thread safe event raising.
    /// </summary>
    /// <param name="e">The event argument.</param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // Recommended is to avoid reentry 
        // in collection changed event while collection
        // is getting changed on other thread.
        using (BlockReentrancy())
        {
            if (!this.suspendCollectionChangeNotification)
            {
                NotifyCollectionChangedEventHandler eventHandler =
                      this.CollectionChanged;
                if (eventHandler == null)
                {
                    return;
                }

                // Walk thru invocation list.
                Delegate[] delegates = eventHandler.GetInvocationList();

                foreach
                (NotifyCollectionChangedEventHandler handler in delegates)
                {
                    // If the subscriber is a DispatcherObject and different thread.
                    DispatcherObject dispatcherObject
                         = handler.Target as DispatcherObject;

                    if (dispatcherObject != null
                           && !dispatcherObject.CheckAccess())
                    {
                        // Invoke handler in the target dispatcher's thread... 
                        // asynchronously for better responsiveness.
                        dispatcherObject.Dispatcher.BeginInvoke
                              (DispatcherPriority.DataBind, handler, this, e);
                    }
                    else
                    {
                        // Execute handler as is.
                        handler(this, e);
                    }
                }
            }
        }
    }
}

它很慢并且会冻结 UI,并且会延迟图像显示,直到整个列表加载完成。我该如何解决这个问题?我 2018 年的旧项目与上面的代码相同,工作得很好,速度很快,而且没有阻塞 ui。但对于 .net 8.0 来说则不然。我不明白微软改变了什么。

c# wpf asynchronous data-binding listbox
1个回答
0
投票

我建议您在另一个线程中执行 OnLoad 方法的整个主体。
我也不明白为什么你对一项任务使用

var tasks = new List<Task>();
?这是代码复制错误吗?该示例是否未成功缩短? 示例:

        private async void OnLoad() => await Task.Run(async () =>
        {
            var tasks = new List<Task>();
            if (Service.PopularMovies.Count == 0)
            {
                tasks.Add(Service.GetPopularMoviesAsync("en", 1));
            }
            await Task.WhenAll();
        });

此外,您很可能需要冻结 ImageSource (BitmapImage):

        public static BitmapImage GetImage(string url, ImageType imageType)
        {
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.UriSource = new Uri(url);
            bitmapImage.EndInit();
            bitmapImage.Freeze(); 
            return bitmapImage;
        }
© www.soinside.com 2019 - 2024. All rights reserved.