WPF's Imagecontrol freezes the UI
本文关键字:freezes the UI Imagecontrol WPF | 更新日期: 2023-09-27 18:35:28
我想在我的WPF应用程序中显示一个用户引力。这是我绑定图像控件的方式:
<Image Source="{Binding Path=Email, Converter={StaticResource GravatarConverter},IsAsync=True}">
其中GravatarConverter返回给定电子邮件的URL。不幸的是,这在加载第一个图像时完全阻止了我的 UI。请注意,我使用的是"IsAsync=True"。经过一些研究,我发现在应用程序启动的单独线程中调用FindServicePoint时,我可以解决此问题:
Task.Factory.StartNew( () => ServicePointManager.FindServicePoint( "http://www.gravatar.com", WebRequest.DefaultWebProxy ) );
但是,当我的应用程序已经在下载图像时,当FindServicePoint尚未完成时,这是行不通的。有人可以解释为什么WPF应用程序需要这个FindServicePoint,为什么这会阻止UI以及如何避免阻止?
谢谢
更新:事实证明,在Internet Explorers"Internet选项"->"连接"->"LAN设置"中取消选中"自动检测设置"后,我的问题消失了。
我使用这个非常简单的 WPF 应用程序来重现问题,只需在文本框中插入图像的 url 并单击按钮即可。启用"自动检测设置"后,应用程序会在首次加载图像时冻结几秒钟。使用此选项立即禁用其加载。
MainWindow.xaml
<Window x:Class="WpfGravatarFreezeTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" x:Name="tbEmail" />
<Button Grid.Column="0" Grid.Row="0" Click="buttonLoad_OnClick" HorizontalAlignment="Right">Set Source</Button>
<Image x:Name="img" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" />
</Grid>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media.Imaging;
namespace WpfGravatarFreezeTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void buttonLoad_OnClick( object sender, RoutedEventArgs e )
{
try { this.img.Source = new BitmapImage(new Uri(this.tbEmail.Text)); }
catch( Exception ){}
}
}
}
阻止 UI happans,因为 IsAsync=True 仅以异步方式运行绑定进程。在您的情况下,您在转换过程中有一个长时间运行的操作。要解决此问题,您应该创建异步显示结果的转换器,如下所示(基于此答案):
创建任务编译通知程序:
public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged
{
public TaskCompletionNotifier(Task<TResult> task)
{
Task = task;
if (task.IsCompleted) return;
task.ContinueWith(t =>
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs("Result"));
}
});
}
// Gets the task being watched. This property never changes and is never <c>null</c>.
public Task<TResult> Task { get; private set; }
// Gets the result of the task. Returns the default value of TResult if the task has not completed successfully.
public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
public event PropertyChangedEventHandler PropertyChanged;
}
创建实现标记扩展的异步转换器:
public class ImageConverter: MarkupExtension, IValueConverter
{
public ImageConverter()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return new BitmapImage();
var task = Task.Run(() =>
{
Thread.Sleep(5000); // Perform your long running operation and request here
return value.ToString();
});
return new TaskCompletionNotifier<string>(task);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
在 Xaml 中使用它:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox x:Name="uri" Grid.Row="0" Text="{Binding ImageUri, ElementName=main}"/>
<Image Grid.Row="1" DataContext="{Binding Text, ElementName=uri, Converter={local:ImageConverter}}" Source="{Binding Path=Result, IsAsync=True}"/>
</Grid>
更新 2似乎图像控件本身异步加载图像。你是对的,第一次加载需要很多时间。您可以使用如下代码:
try
{
var uri = Uri.Text;
var client = new WebClient();
var stream = await client.OpenReadTaskAsync(uri);
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = stream;
source.EndInit();
Img.Source = source;
}
catch (Exception) { }
但它的性能并不比你的变体好。