65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2012 年 5 月 13 日

Ms-PL

6分钟阅读

viewsIcon

22872

downloadIcon

195

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

更新

  1. 我将 Silverlight 版本与 WPF 版本完全相同,因此您可以在这两个平台上以相同的方式使用 PriorityObject
  2. 现在,存档中还有一个 Windows Phone 7 示例。

原因 

最近,我在 WPF ListBox 中尝试使用 PriorityBinding,但它速度非常慢且不稳定。因此,我尝试编写一个更流畅的替代品。最后,我创建了 PriorityObject,现在我可以显示一个流畅的 ListBox,更新平稳且快速。

回顾:PriorityBinding 是一个具有多个源的绑定,有些源很快,有些源很慢。通常,最慢的绑定具有最好的结果,最快的绑定具有最差的结果。例如,想象一个图像的源:最快的绑定将是一个模糊的缩略图,最慢的将是完整的高分辨率图片。当然,源是有优先级的,所以当最佳源准备就绪时,其他源就会被忽略。在我们的例子中,如果完整图片比缩略图先可用,缩略图将被忽略,只显示图片。

与 PriorityBinding 的比较

NET 的 PriorityBinding 和我的 PriorityObject 之间有三个主要区别。

  1. PriorityObject 使用线程,因此更加“流畅”,尤其是在列表控件(如 ListBox)中。我推测 PriorityBinding 使用 BackgroundWorker,这对于独立的绑定来说很好,但对于列表来说却很混乱。
  2. PriorityBinding 部分在 XAML 本身中进行了描述,这违反了视觉描述与对象实现分离的规则。相反,PriorityObject 使 XAML 保持普通,而对象实现负责管理内容(通过线程)。
  3. PriorityObject 与 Silverlight 兼容。

其他优点

您的值可以是惰性的(按需评估)。只需在调用 PriorityObject 的构建器时选择 Lazy=true。当 ListBox 不总是显示时(例如,它可能在一个选项卡或一个辅助窗口中),这非常有用。

不便之处:您的过程在线程中进行评估。因此,您需要处理 WPF 对象的线程问题。我对此无能为力,这是 WPF 的一个重大限制,但您可以使用 Dispatchers 在 UI 线程(或您的对象的 Dispatcher)上执行部分工作。无论如何,这个问题与线程和 WPF 有关,所以无论您是否使用 PriorityObject,都无法避免并行编程。

并行启动两个 ListBox 的示例

两个 ListBox 都显示两个文本,一个具有优先评估函数(左侧,文本 #1),一个普通的(文本 #2)。左侧文本(#1)有三个状态:

  1. 开始时,它有一个默认值:“快速文本 #1”。
  2. 3 秒后,它获得一个新值:“较慢的文本 #1”。
  3. 再过 2 秒后,它获得最终值:“最慢的文本 #1”。

左侧的 ListBox 使用 .NET 的 PriorityBinding,右侧的 ListBox 使用 PriorityObject,并带有一个数字作为附加列(其值在 2 秒后更改)。

我同时填充了两个 ListBoxes。正如您在此屏幕截图中所见,6 秒后,PriorityBindings 尚未完成,它们甚至在不同行上混合了三种状态。它们需要 10 秒才能完成列表更新。相反,PriorityObjects 在正确的时间完成其任务,正好在 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 的代码,允许其 defineRequestObjectSDispatcher”。

过时的适配 

[ 此段落已过时,除非有一天 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,它基本上允许您在您的程序中使用源代码,无论是开放的还是封闭的。如果您修改了代码,则不必发布您的修改。您可以在此许可证中欣赏的内容:它保证其作者(我)不会对专利提出任何要求(这是现代编程的瘟疫)。许多许可证不包含关于专利的内容,并暴露您(是的,包括最著名的许可证)。有关详细信息,请参见 此处

© . All rights reserved.