PriorityObject:比 PriorityBinding 在列表上下文中(如 ListBox)更流畅,并且与 Silverlight 和 WP7 兼容





4.00/5 (3投票s)
PriorityObject:比 PriorityBinding 在列表上下文中(如 ListBox)更流畅,并且与 Silverlight 和 WP7 兼容。
更新
- 我将 Silverlight 版本与 WPF 版本完全相同,因此您可以在这两个平台上以相同的方式使用
PriorityObject
。 - 现在,存档中还有一个 Windows Phone 7 示例。
原因
最近,我在 WPF ListBox
中尝试使用 PriorityBinding,但它速度非常慢且不稳定。因此,我尝试编写一个更流畅的替代品。最后,我创建了 PriorityObject
,现在我可以显示一个流畅的 ListBox,更新平稳且快速。
回顾:PriorityBinding
是一个具有多个源的绑定,有些源很快,有些源很慢。通常,最慢的绑定具有最好的结果,最快的绑定具有最差的结果。例如,想象一个图像的源:最快的绑定将是一个模糊的缩略图,最慢的将是完整的高分辨率图片。当然,源是有优先级的,所以当最佳源准备就绪时,其他源就会被忽略。在我们的例子中,如果完整图片比缩略图先可用,缩略图将被忽略,只显示图片。
与 PriorityBinding 的比较
NET 的 PriorityBinding
和我的 PriorityObject
之间有三个主要区别。
PriorityObject
使用线程,因此更加“流畅”,尤其是在列表控件(如 ListBox)中。我推测PriorityBinding
使用BackgroundWorker
,这对于独立的绑定来说很好,但对于列表来说却很混乱。PriorityBinding
部分在 XAML 本身中进行了描述,这违反了视觉描述与对象实现分离的规则。相反,PriorityObject
使 XAML 保持普通,而对象实现负责管理内容(通过线程)。PriorityObject
与 Silverlight 兼容。
其他优点
您的值可以是惰性的(按需评估)。只需在调用 PriorityObject
的构建器时选择 Lazy=true
。当 ListBox 不总是显示时(例如,它可能在一个选项卡或一个辅助窗口中),这非常有用。
不便之处:您的过程在线程中进行评估。因此,您需要处理 WPF 对象的线程问题。我对此无能为力,这是 WPF 的一个重大限制,但您可以使用 Dispatchers 在 UI 线程(或您的对象的 Dispatcher)上执行部分工作。无论如何,这个问题与线程和 WPF 有关,所以无论您是否使用 PriorityObject
,都无法避免并行编程。
并行启动两个 ListBox 的示例
两个 ListBox 都显示两个文本,一个具有优先评估函数(左侧,文本 #1),一个普通的(文本 #2)。左侧文本(#1)有三个状态:
- 开始时,它有一个默认值:“快速文本 #1”。
- 3 秒后,它获得一个新值:“较慢的文本 #1”。
- 再过 2 秒后,它获得最终值:“最慢的文本 #1”。
左侧的 ListBox 使用 .NET 的 PriorityBinding
,右侧的 ListBox
使用 PriorityObject
,并带有一个数字作为附加列(其值在 2 秒后更改)。
我同时填充了两个 ListBoxes。正如您在此屏幕截图中所见,6 秒后,PriorityBindings 尚未完成,它们甚至在不同行上混合了三种状态。它们需要 10 秒才能完成列表更新。相反,PriorityObject
s 在正确的时间完成其任务,正好在 5 秒后(正如我们所期望的)。
代码示例
让我们来实现上面的例子,其中 ListBox 显示由两个文本和一个数字组成的项。第一个文本和数字基于 PriorityObject
,第二个文本是普通绑定,所以我不描述它。
XAML
<ListBoxx:Name="AdvancedListBox"Width="280">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinitionWidth="115"/>
<ColumnDefinitionWidth="115"/>
<ColumnDefinitionWidth="40"/>
</Grid.ColumnDefinitions>
<!-- We have to add '.Value' for PriorityObject: -->
<TextBlockGrid.Column="0"Text="{Binding Text1.Value}"/>
<!-- An ordinary static text: -->
<TextBlockGrid.Column="1"Text="{Binding Text2}"/>
<!-- We don't need to add '.Value' here because Double1 encapsulates _Double1.Value: -->
<TextBlockGrid.Column="2"Text="{Binding Double1}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
正如您所见,我们有三个绑定:
Text1.Value
,直接绑定到PriorityObject<string>
的内容。Text2
,绑定到一个普通的字符串,具有 get/set。- Double1,通过
Double1 : get { return _Double1.Value; }
间接绑定到PriorityBinding<string>
。这需要在 CS 中进行两个定义,但优点是在 XAML 中更清晰。
CS
项目类
public class AdvancedAsyncListItemWithPriorityObject : INotifyPropertyChanged
项目类必须实现 INotifyPropertyChanged
接口。
Text1
属性
public PriorityObject<string> Text1 { get; set; }
我们需要 get 和 set,否则 ListBox 将不会更新文本。
在类构建器中的初始化
var valueDelegatesForText1 = new PriorityObjectDelegates<string>();
// First evaluation function: slowest & best
valueDelegatesForText1.Add(
() =>
{ Thread.Sleep(5000); // This simulates a lengthy time before the data being bound to is actualy available.
return "SloweST text #1"; });
// Second evaluation function: quicker (but sill slow) and worse
valueDelegatesForText1.Add(
() =>
{ Thread.Sleep(3000); // This simulates a lengthy time before the data being bound to is actualy available.
return "Slower text #1"; });
Text1 = new PriorityObject<string>(
"Text1", this.NotifyPropertyChanged, valueDelegatesForText1, true,
"Default quickest text"); // a default quick but worst text.
我部署了这个例子,以展示其结构。请参阅 Double1 获取简洁的示例。
Double 1
属性
private PriorityObject<double> _Double1;
// Double1 encapsulates _Double1 in order to avoid the addition of ".Value" in the XAML:
public double Double1
{ get { return _Double1.Value; } set { _Double1.Value = value; } }
为了简化 XAML 中的绑定,我们使用 Double1 来访问 _Double1.Value
。
在类构建器中的初始化
_Double1 = new PriorityObject<double>
("Double1", this.NotifyPropertyChanged, new PriorityObjectDelegates<double>()
{ ()=>
{ // This simulates a lengthy time before the data being bound to is actualy available.
Thread.Sleep(2000);
return 2000.0; }
}, false);
这个例子比 Text1
的例子更紧凑。并且只有一个线程/评估/源函数,因为即使只有一个源,PriorityObject
也比普通的线程编程更容易。请注意,我们将“Double1”作为通知名称,而不是“_Double1”,因为 ListBox
绑定到 Double1
。
INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
// This standard procedure is necessary to PriorityObject.
private void NotifyPropertyChanged(string info)
{ if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
INotifyPropertyChanged
的标准实现。请注意,我们绝对需要 NotifyPropertyChanged
过程来处理 PriorityObject
。
Silverlight / Windows Phone 7 中的差异
目前没有区别。
我最初为 Silverlight 编写了一个适配器,因为它需要正确的 Dispatcher 来调用 NotifyPropertyChanged
过程。
看来 Silverlight 只使用一个 dispatcher,所以我修改了我的代码,那么以下段落现在就没有用了,直到未来版本的 Silverlight 使用多个 dispatchers。在这种情况下,您可以修改 PriorityObject.cs 的代码,允许其 define
“RequestObjectSDispatcher
”。
过时的适配
[ 此段落已过时,除非有一天 Silverlight 使用多个 Dispatchers(所以我不删除它)]
由于 Silverlight 的限制,存在一些细微差别。
主要是,我们需要知道实例化 PriorityObject-based 项类的类/线程/函数的 Dispatcher。
这是代码的适配:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var list = new
ObservableCollection<AdvancedAsyncListItemWithPriorityObject>();
for (int i = 0; i < 10; i++)
list.Add(new
AdvancedAsyncListItemWithPriorityObject(
#if SILVERLIGHT
this.Dispatcher
#endif
));
this.AdvancedListBox.ItemsSource = list;
}
正如您所见,我们需要将 dispatcher 传递给项类的构建器。这很简单,通常有一个 this.Dispatcher
。如果没有,请尝试 null(PriorityObject
将使用默认 dispatcher)。
类构建器的示例
public AdvancedAsyncListItemWithPriorityObject(
System.Windows.Threading.Dispatcher
CreatorSDispatcher) // With Silverlight, we need to know the Dispatcher that created this instance.
然后,项构建器将相同的 dispatcher 传递给 PriorityObject 的构建器。
_Double1 = new PriorityObject<double>
("Double1", this.NotifyPropertyChanged, new PriorityObjectDelegates<double>()
{ ()=>
{
Thread.Sleep(2000); return 2000.0;
}
}, CreatorSDispatcher, false);
在这里,与 Dot.net 代码的唯一区别是添加了这个参数:CreatorSDispatcher(通俗地说:创建者的 dispatcher
)。
下载源代码和一些示例
该存档包含 PriorityObject
的源代码和两个示例项目:一个用于 WPF/>NET,一个用于 Silverlight。
常见问题解答
- 我们真的需要为
PriorityObject
提供NotifyPropertyChanged
过程吗?为什么我们不直接在PriorityObject
中访问PropertyChanged
? - 答:
PropertyChanged
是一个事件,并且事件在 C# 中以一种不寻常的方式编译,所以我们不能在它的类之外调用它。这就是为什么我们需要将NotifyPropertyChanged
引用传递给PriorityObject
的构建器(因为NotifyPropertyChanged
不是INotifyPropertyChanged
的正式组成部分,尽管它看起来很奇怪)。 - 在 Silverlight 中,我们能从正在构建的
PriorityObject
实例中提取 Dispatcher 吗? - 答:是的,我更新了我的代码以在内部使用 SL 应用程序实例化的唯一 Dispatcher。
开源
此代码的许可证是 Ms-PL,它基本上允许您在您的程序中使用源代码,无论是开放的还是封闭的。如果您修改了代码,则不必发布您的修改。您可以在此许可证中欣赏的内容:它保证其作者(我)不会对专利提出任何要求(这是现代编程的瘟疫)。许多许可证不包含关于专利的内容,并暴露您(是的,包括最著名的许可证)。有关详细信息,请参见 此处。