我想在我的WPF应用程序中显示用户Gravatar。这就是我绑定Image-Control的方式:
<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-App完全需要这个FindServicePoint,为什么这会阻止UI以及如何避免阻塞?
由于
更新:事实证明,在我在Internet Explorers“Internet选项” - >“连接” - >“局域网设置”中取消选中“自动检测设置”后,我的问题就消失了。
我使用这个非常简单的WPF-Application来重现问题只需在文本框中插入图像的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 ){}
}
}
}
答案 0 :(得分:2)
阻止UI开心,因为 IsAsync = True 仅以异步方式运行绑定过程。在您的情况下,您在转换过程中有一个长时间运行的操作。要解决这个问题,你应该创建一个转换器,像这样异步地呈现结果(基于this answer):
创建任务合并通知程序:
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;
}
创建实施MarkupExtention的异步转换器:
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) { }
但它的表现并不比你的变体好。