using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media.Imaging; using System.Windows.Threading; using Work; namespace _29WxOrderWpf.Converters { public class ImageUrlToBitmapImageConverter : IMultiValueConverter { /// 缓存上限,防止大量缩略图重复下载导致内存膨胀 private const int MaxCacheCount = 200; private readonly ConcurrentDictionary _imageCache = new ConcurrentDictionary(); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Length == 0 || values[0] == DependencyProperty.UnsetValue) return null; var url = values[0] as string; var isLoad = values[1] is bool b && b; if (!isLoad || string.IsNullOrEmpty(url)) return null; // 或返回占位图 if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) { if (_imageCache.TryGetValue(url, out BitmapImage cached)) { return cached; } try { // 直接加载绑定如果遇到有问题的网络图片会直接崩溃。 BitmapImage bitMap = AllBase.ImageBuild.LoadBitmipImageFromUrl(url); if (bitMap != null) { if (_imageCache.Count >= MaxCacheCount) { _imageCache.Clear(); } _imageCache[url] = bitMap; } return bitMap; } catch { } } return null; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } // 建议作为静态字段复用,不要每次调用都 new HttpClient() private static readonly HttpClient _httpClient = new HttpClient(); } public class ImageLoadRequest { public string Url { get; set; } public bool IsLoad { get; set; } // 用于避免相同状态重复触发下载逻辑 public override bool Equals(object obj) => obj is ImageLoadRequest other && other.Url == Url && other.IsLoad == IsLoad; public override int GetHashCode() => Url.GetHashCode(); } public class ImageLoadRequestConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var url = values[0] as string; var isLoad = values.Length > 1 && values[1] is bool b && b; return new ImageLoadRequest { Url = url, IsLoad = isLoad }; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); } public static class AsyncImageLoader { private static readonly HttpClient _httpClient = new HttpClient(); private static readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); public static readonly DependencyProperty RequestProperty = DependencyProperty.RegisterAttached( "Request", typeof(ImageLoadRequest), typeof(AsyncImageLoader), new PropertyMetadata(null, OnRequestChanged)); public static ImageLoadRequest GetRequest(DependencyObject obj) => (ImageLoadRequest)obj.GetValue(RequestProperty); public static void SetRequest(DependencyObject obj, ImageLoadRequest value) => obj.SetValue(RequestProperty, value); private static readonly DependencyProperty CurrentUrlProperty = DependencyProperty.RegisterAttached("CurrentUrl", typeof(string), typeof(AsyncImageLoader), new PropertyMetadata(null)); private static string GetCurrentUrl(DependencyObject obj) => (string)obj.GetValue(CurrentUrlProperty); private static void SetCurrentUrl(DependencyObject obj, string value) => obj.SetValue(CurrentUrlProperty, value); private static readonly DependencyProperty CtsProperty = DependencyProperty.RegisterAttached("Cts", typeof(CancellationTokenSource), typeof(AsyncImageLoader), new PropertyMetadata(null)); private static CancellationTokenSource GetCts(DependencyObject obj) => (CancellationTokenSource)obj.GetValue(CtsProperty); private static void SetCts(DependencyObject obj, CancellationTokenSource value) => obj.SetValue(CtsProperty, value); private static void OnRequestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is Image image)) return; // 值没变(Equals 重写过),不做任何事,避免无意义触发 if (Equals(e.OldValue, e.NewValue)) return; var request = e.NewValue as ImageLoadRequest; GetCts(image)?.Cancel(); if (request == null || !request.IsLoad || string.IsNullOrEmpty(request.Url)) { return; // 滚动中,不加载,保留原图,不产生任何开销 } string url = request.Url; SetCurrentUrl(image, url); var cts = new CancellationTokenSource(); SetCts(image, cts); // 关键:把真正的下载动作丢到 Dispatcher 队列里、用较低优先级去做, // 避免在当前这一帧的布局/绑定更新过程中做任何"重"的同步开销, // 也顺便起到一个天然的"防抖"效果——如果这一帧内 Request 又变了 // (比如滚动中又切回别的 item),上面的 Cancel() 已经先取消了。 image.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(async () => { if (cts.IsCancellationRequested) return; try { var bitmap = await LoadBitmapAsync(url, cts.Token); if (bitmap != null && GetCurrentUrl(image) == url && !cts.IsCancellationRequested) { image.Source = bitmap; } } catch (OperationCanceledException) { } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error loading image from {url}: {ex.Message}"); } })); } private static Task LoadBitmapAsync(string url, CancellationToken token) { return _cache.GetOrAdd(url, async u => { using var response = await _httpClient.GetAsync(u, token); response.EnsureSuccessStatusCode(); var data = await response.Content.ReadAsByteArrayAsync(); using var stream = new MemoryStream(data); var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.CreateOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile; bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.StreamSource = stream; bitmap.DecodePixelWidth = 200; bitmap.EndInit(); bitmap.Freeze(); return bitmap; }); } } }