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

ASP.NET 中的 WPF 双向数据绑定 - 启用 MVVM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (35投票s)

2011 年 1 月 14 日

CPOL

21分钟阅读

viewsIcon

145596

downloadIcon

1839

将类似 WPF 的声明性数据绑定引入 ASP.NET Web Forms,以实现对任何对象的声明性双向数据绑定,同时开启 MVVM UI 开发。

bf.jpg

引言

从一开始,ASP.NET Web Forms 中的双向数据绑定支持就很差。随着时间的推移,出现了一些解决方案(请继续阅读以了解其中一些)。不幸的是,所有这些方案都有显著的局限性,或者只有在特定方式下使用时才有效。

任何从 Silverlight 或 WPF 项目转到 Web Forms 的人都会发现自己必须将思维方式从有状态世界调整到无状态世界。这种转变迫使人们做出一些让步,其中最明显的一个就是缺少前者框架提供的丰富灵活的双向数据绑定模型。WPF 提供的强大数据绑定支持也使得 MVVM 模式的使用达到了如此程度,以至于数据绑定 + MVVM 的组合已成为使用 WPF 和 Silverlight 开发 UI 应用程序的事实标准模式。许多人会同意,在花费了大量时间使用 WPF 或 Silverlight 之后,在设计 Web Forms 页面时无法使用此模式感觉就像是倒退了一大步。

所提出框架的目标是通过允许使用 WPF 风格的声明性语法,同时允许使用 MVVM 模式进行 UI 开发,从而解决 ASP.NET Web Forms 中缺乏灵活强大的双向数据绑定问题。

请注意,在本文中,“绑定”一词用于描述在屏幕上显示 ViewModel/源数据的操作。“解除绑定”一词用于描述此过程的反向操作:从控件中提取用户输入数据并将其映射回 ViewModel。

背景

参考/概念

我假设您熟悉的一些概念

当前解决方案

解决方案 主要缺点
所有控件的子类 您必须对每个需要支持双向绑定的控件进行子类化!
数据源控件和 GridView、FormView、DetailsView 您仅限于使用列出的控件。需要工厂方法/CRUD 方法和参数映射。
使用 Visual Studio 设计器在设计时创建绑定 无运行时支持。您必须使用 Visual Studio 设计器。
每个绑定的扩展器控件 每个绑定都需要一个绑定扩展控件,这可能导致 ASPX 文件臃肿。
运行时解析 ASP.NET 源文件 在跨母版页/内容页和用户控件进行绑定时受限,因为您是从文件系统读取源的。
绑定管理器 没有内联 (ASPX) 声明式绑定。
手动操作 劳动密集型、冗长、易出错、可维护性差、代码臃肿。

注:上面列出的为 Web Forms 提供双向数据绑定支持的解决方案绝不是详尽无遗的,但我确实觉得它涵盖了一些更常用的方法。

您可能会决定偏爱上述选项中的某一个而不是所提出的框架。我列出它们正是出于这个原因,不同的场景需要不同的解决方案,并且了解周围有什么是值得的。以上所有方法都能很好地工作,并且可以适应各种架构设计,但我认为所提出的框架提供了一些优势。我希望通过解释我在此框架中采用的方法,能让您相信这一点。如果您认为对可用的双向数据绑定方法进行更深入的检查会有益处,那么请留言,我将考虑扩展上述方法的优点和缺点,但我确信,如果您花时间用 WPF 方式进行数据绑定,那么您会立即明白我所采用的方法的益处。

设计原则

以下是我在开发此框架时努力遵循的一些理想原则

  • 无页面基类 - 为了使此框架能够轻松地与现有框架集成,其中一些框架要求您的页面继承自基类,因此我们决定不要求这样做。
  • 最小化连接代码 - 一个关键目标是最大限度地减少所需的连接量。与“无页面基类”目标相结合,这需要仔细的设计和实现。
  • 最小化代码隐藏 - 尽可能消除对任何代码隐藏的需求。完全声明式地实现 UI 的能力是一个关键目标。
  • 促进 ASP.NET 中的 MVVM - 完全支持命令绑定和双向数据绑定。
  • 模仿 WPF - 允许使用 WPF 绑定功能,例如 IValueConverter、绑定模式、资源和富有表现力的声明性绑定语句。
  • 抑制异常 - 与 WPF 一样,数据绑定错误不应导致您的应用程序抛出异常(部分实现,请参阅:“我们进展如何?”)。

已实现的功能...

  • 单向和双向数据绑定
  • 少量集成代码
  • IValueConverter 支持
  • 隐式和显式优先级 - 如果您将多个控件绑定到单个源,则可以在解除绑定时控制哪个控件“获胜”/“具有权威性”
  • 全局绑定资源 - 允许一个绑定声明在多个控件中重复使用
  • 有状态和无状态绑定 - 选择在回发之间在视图状态中保留 ViewModel,或每次重新创建它
  • 级联更新(有关更多信息,请参阅同标题部分)
  • 自动或显式解除绑定 - 选择让框架在每次回发时自动解除绑定,或在需要时手动启动解除绑定操作
  • 完全可单元测试 - 利用依赖注入/控制反转以允许轻松模拟
  • 支持深度绑定路径 - 将愉快地遍历并绑定到子属性(对象图的大小没有限制)
  • 完全支持声明式绑定
  • 部分支持编程绑定
  • 无需基类 - 允许与现有框架轻松集成
  • 完全支持静态或动态绑定 - 使用预定义的数据绑定或根据应用程序流/数据流生成它们
  • 相对或绝对绑定路径 - 使用绝对绑定表达式“脱离上下文”绑定,或作为嵌套层次结构的一部分使用相对绑定路径绑定
  • 完全适用于嵌套场景 - 数据上下文从页面或父绑定继承

...还有一些未实现的功能

  • 属性强制转换
  • 内置验证
  • 无需 INotifyPropertyChanged 的级联更新 - 可能,因为我们**知道**值何时已更改
  • UI 元素绑定 - 绑定到其他控件属性的能力(类似于 WPF)
  • 祖先绑定 - 绑定到特定类型的祖先的能力
  • 自定义视图状态序列化程序 - 在使用 INotifyPropertyChanged 时避免需要 [field: NonSerialized]
  • 支持 IDataErrorInfo
  • 拥有多个模型/上下文的能力 - 通过基于字典的视图数据上下文集合利用多个 View Models/数据上下文

Using the Code

解决方案概览

项目 描述
框架/绑定 这是核心框架程序集。它包含所有必要的非平台特定框架组件。
框架/ASP绑定 ASP.NET 特定框架组件。
框架测试 单元测试。
领域模型 示例和演示使用的业务对象。
绑定示例 示例和演示库 - 这包含大量简单和更高级的示例,应被视为进一步探索此框架的主要参考。
骨架 使用此框架的最小“hello world”示例。

简单示例

我觉得开始介绍代码的最佳方式是使用一个简单的例子。我将介绍复制 _BareBones/BindingExample.aspx_ 页面和支持类所需的步骤。

最好的起点是创建 ViewModel。任何花时间使用 MVVM 的人都会认识到,这是使用该模式最有益的部分之一——它鼓励在开发新 UI 时采用一种有节制、以数据/操作为中心的方法。

[Serializable]
public class ViewModel
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CreatedDate { get; set; }

    public ClickCommand OnClick
    {
        get
        {
            return new ClickCommand();
        }
    }

    public ViewModel()
    {
        //Just some default values so we see something on the screen.
        //In a real world scenerio, these would be loaded from the model.
        ID = 1;
        FirstName = "Dave";
        LastName = "Smith";
        CreatedDate = new DateTime(1983, 07, 01);
    }
}

没有什么复杂或可怕的。只是一个简单的类,它公开了一些我们将绑定的属性。注意 `SerializableAttribute`,只有当我们打算使用 `StateMode.Persist`(它将 ViewModel 存储在 View State 中)时才需要它。另一个可能引人注目的是 `ClickCommand`。这只是 `ICommand` 的一个实现,如下所示

public class ClickCommand : ICommand
{
    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        
    }

    #endregion
}

上述 `ICommand` 不执行任何操作,但演示了如何将命令连接到通过 ViewModel 定义的方法。

接下来,我们将此 ViewModel 设置为页面的数据上下文。我们通过实现 `IBindingContainer` 接口并从页面代码隐藏的重写的 `onload` 事件中调用一个(扩展)方法来完成此操作。

//two using statements
using Binding.Interfaces;
using Binding;

namespace Barebones
{
    /*Implement one interface, with one property only*/
    public partial class BindingExample : System.Web.UI.Page, IBindingContainer
    {

        #region IBindingContainer Members

        private object dataContext = new ViewModel();
        public object DataContext
        {
            get { return dataContext; }
            set { dataContext = value; }
        }

        #endregion

        //override one method
        protected override void OnLoad(EventArgs e)
        {
            //call one method
            this.RegisterForBinding();
            base.OnLoad(e);
        }
       
    }
}

上面极其简单的代码通过将我们的 ViewModel 作为 `DataContext` 属性的值返回来设置页面数据上下文,并通过调用 `RegisterForBinding()` 将页面注册为绑定容器。

这就是开始使用 MVVM 并利用双向数据绑定所需的所有设置。

下一步是创建我们的视图(即控件和一些绑定)。

<%--Import 1 namespace and as far as markup is concerned you're ready to go! --%>
<%@ Import Namespace="Binding" %>


<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <!--Two-way (default) data bindings-->
        ID: <asp:Label ID="lbID" runat="server" 
                Text='<%# sender.Bind("ID") %>'></asp:Label>
        <br />
        First Name: <asp:TextBox ID="tbFirstName" runat="server" 
                        Text='<%# sender.Bind("FirstName") %>'></asp:TextBox>
        Last Name: <asp:TextBox ID="tbSecondName" runat="server" 
                       Text='<%# sender.Bind("LastName") %>'></asp:TextBox>
        Date: <asp:TextBox ID="tbCreatedDate" runat="server" 
                   Text='<%# sender.Bind("CreatedDate") %>'></asp:TextBox>
        <!-- Command Binding -->
        <asp:Button ID="btnSubmit" runat="server" 
            OnClick='<%# sender.BindC("OnClick") %>' Text="Submit"/>
    </div>
    </form>
</body>
</html>
-->

我们现在准备运行示例。结果如下

a screenshot of the simple example running

要测试上述内容,我建议在代码隐藏中添加一个 `Page_PreRender` 处理程序并设置一个断点。同时在 `ClickCommand` 的 `Execute` 方法中放置一个断点。修改文本框的值并单击提交。首先会命中 `Execute` 方法中的断点,然后是 `Page_PreRender` 中的断点。检查 `DataContext` 属性 (ViewModel) 以查看文本框中未绑定的值。以这种方式使用 `Page_PreRender` 是所有提供的示例中推荐的 ViewModel/DataContext 验证方法,因为在 PLC 的这一点上,所有绑定相关的内容都应该已经运行。

有些值得解释,但有些人会挠头的问题:`sender.Bind("...")` 和 `sender.BindC("...")`。

“sender”?在哪里?什么?为什么?这与利用 ASP.NET 数据绑定生命周期有关,不幸的是,它在可扩展性方面没有提供太多东西,所以这实际上是一个“hack”,以便我们获得一个可以从中挂起框架其余部分的钩子。当然,还有其他方法可以做到这一点:来自标记的静态方法调用、页面基类、事件处理程序、受保护的代码隐藏方法——但所有这些都要求更多的代码隐藏,或者从我们试图避免的共同超类继承。那么,“sender”从何而来?请参阅上面示例页面中以下反编译的方法

public void __DataBindingtbFirstName(object sender, EventArgs e)
{
    TextBox dataBindingExpressionBuilderTarget = (TextBox) sender;
    BindingExample Container = 
      (BindingExample) dataBindingExpressionBuilderTarget.BindingContainer;
    dataBindingExpressionBuilderTarget.Text = 
      Convert.ToString(BindingHelpers.Bind(sender, "FirstName"), 
      CultureInfo.CurrentCulture);
}

我们在标记中编写的数据绑定语句在上述方法的范围内执行。我们还定义了以下扩展方法(以及其他方法)

/// <summary>
/// Bind with the default options
/// </summary>
/// <param name="control" />
/// <param name="sourcePath" />
/// <returns>
public static object Bind(this object control, string sourcePath)
{
    return Bind(control, new Options { Path=sourcePath });
}

所以我们用 `sender.Bind` 所做的只是简单地调用 `System.Object` 的一个扩展方法。

当然,这是一个非常简单的示例,但我希望它能演示所提供的框架可以轻松利用。请参阅 _BindingExamples_ 项目(及其菜单 - _MainMenu.aspx_),以获取适用于许多不同场景的更多示例,并演示您可以使用的功能。

探索语法

共有三种绑定类型:(内联)数据、(内联)命令和(全局)资源。这些通过以下绑定方法使用

  • 数据: sender.Bind()
  • 命令: sender.BindC()/sender.BindCommand()
  • 资源: sender.BindR()/sender.BindResource()

(如果指定了两种方法,其中一种只是较长版本的简写语法,以减少标记的冗长性。)

命令和数据绑定不言自明,一个用于数据并支持双向,一个用于命令且仅支持单向。资源绑定作为 WPF 资源的替代品提供,使您能够一次指定一个绑定并在许多控件中重复使用。资源绑定可以是命令绑定或数据绑定;在声明全局资源时通过 `Mode` 属性指定(请参阅 _BindingExamples/Advanced/GridViewExample.aspx_ 以获取实际示例)。

简单绑定只需创建即可

Text='<%# Bind("Expression") %>'
Text='<%# BindC("Expression") %>'
Text='<%# BindR("ResourceID") %>'

还有一个扩展语法,可以对绑定进行更高级别的控制

Text='<%# sender.Bind(new Options{Path="Expression", 
          Converter="LeaveTypeImageConverter", 
          Mode=BindingMode.OneWay, IsAuthorative=true})%>'
Text='<%# sender.BindC(new Options{Path="Expression", 
          Converter="LeaveTypeImageConverter", 
          Mode=BindingMode.Command, IsAuthorative=true})%>'
Text='<%# BindR("ResourceID") %>'

如您所见,扩展语法仅适用于命令和数据绑定,因为资源绑定通过其声明进行控制。指定 `Converter` 时,这应该是转换器的完全限定类型名称。

还提供第三种语法,即上述扩展语法的简写版本(请参阅 _BindingExamples/Simple/BindingOptionsExample.aspx_ 获取演示)

Text='<%# sender.Bind(new {Path = "Type", 
          Converter="LeaveTypeImageConverter", 
          Mode="OneWay", IsAuthorative=true}) %>'
Text='<%# sender.BindC(new {Path = "Type", 
          Converter="LeaveTypeImageConverter", 
          Mode="Command", IsAuthorative=true}) %>'
Text='<%# BindR("ResourceID") %>'

提供的示例确实是开始了解此语法实际应用的最佳场所。我已尝试在 ASP.NET 的限制下使其尽可能自然、直观和类似 WPF。

StateMode - 持久化或不持久化

使用此框架时,您可以在两种 `StateMode` 之间进行选择:`Persist` 和 `Recreate`。

使用 `StateMode.Persist`,页面的 `DataContext` 将在页面请求之间存储在 View State 中,以便您可以针对有状态的 ViewModel 进行操作。这是默认模式,已实现为尽可能模仿以 WPF 为中心的 MVVM。要使用 `StateMode.Persist`,ViewModel 必须是 `[Serializable]`。

使用 `StateMode.Recreate`,页面的 `DataContext` 必须在每次回发时重新创建,因此这是页面开发人员的责任。这可能像实例化 ViewModel 的新实例并通过页面 `DataContext` 属性的 getter 返回它一样简单。

我觉得选择正确的 `StateMode` 将取决于具体场景,所以我把它留给消费者选择最适合情况的模式。

控制 `StateMode` 可以按页面或站点范围进行。如果为页面指定了模式,它将覆盖任何站点范围的设置。

要为整个站点设置 `StateMode`,请在 _web.config_ 中使用以下内容

<appSettings>
    <add key="BindingStateMode" value="Persist"/>
</appSettings>

或者

<appSettings>
    <add key="BindingStateMode" value="Recreate"/>
</appSettings>

要逐页设置,请使用 `BindingOptionsControl`(请参阅:_BindingExamples/Advanced/NestedStatelessBinding.aspx_)。

<Binding:BindingOptionsControl ID="bindingOptions" runat="server" StateMode="Persist" />

或者

<Binding:BindingOptionsControl ID="bindingOptions" runat="server" StateMode="Recreate" />

控制解除绑定

在启动解除绑定操作时,您可以选择简单自动化——`UpdateSourceTrigger.PostBack`——和完全控制——`UpdateSourceTrigger.Explicit`。

`UpdateSourceTrigger.PostBack` 将在每次回发时将视图解除绑定到 ViewModel,以便在每次回发后,在 `Load` 之后随时都可以获取最新的 UI 数据。

`UpdateSourceTrigger.Explicit` 仅在您明确告知时才会解除视图绑定。要在此模式下启动解除绑定,您应该从页面调用 `this.Unbind()`,或从 ViewModel 或 `ICommand` 调用 `BinderBase.ExecuteUnbind()`。

请参阅 _DomainModel\ViewModels\RegistrationFormExample.aspx_ 和 _DomainModel\ViewModels\RegistrationFormExplicitExample.aspx_ 以获取使用这两种方法的示例。

与 `StateMode` 一样,`UpdateSourceTrigger` 可以通过 _web.config_ 在站点范围内设置,也可以使用 `BindingOptionsControl` 逐页设置。

为整个站点用户设置 `UpdateSourceTrigger`

<appSettings>
    <add key="UpdateSourceTrigger" value="PostBack"/>
</appSettings>

或者

<appSettings>
    <add key="UpdateSourceTrigger" value="Explicit"/>
</appSettings>

要逐页设置,请使用

<Binding:BindingOptionsControl ID="BindingOptionsControl1" 
         runat="server" UpdateSourceTrigger="PostBack" />

或者

<Binding:BindingOptionsControl ID="BindingOptionsControl1" 
         runat="server" UpdateSourceTrigger="Explicit" />

深入探讨

我不会尝试详细说明这个框架是如何构建的,因为这篇文章已经很长了,我的目标是提供一个起点、一些背景信息,并对一些我认为不那么明显且可能在您解释/使用此框架时遇到的方面提供一些见解。这并不是说我认为解释框架的实现方式没有价值,如果人们想要一篇后续文章——一次深度探讨——那么请提出,我就会着手编写。

关注点

关于上下文的说明

创建绑定表达式时,要记住的最重要信息是该语句将应用的上下文。换句话说,当我编写:`Employee.FirstName` 时,我正在针对哪个对象的属性。在简单的情况下,答案很简单:页面的 `DataContext`(您的 ViewModel)。然而,生活并非总是简单的,因为情况并非总是如此。对**“上下文是什么?”**更精确的答案是页面的 `DataContext` **_或_**父绑定返回的对象,以最接近(最近的祖先)者为准。“父绑定”仅适用于通过框架绑定的情况;标准 ASP.NET 数据绑定或数据源的程序化赋值不计为父绑定,在这种情况下,上下文仍将是页面的 `DataContext`。打包的源代码包含一个示例,展示了这一概念的实际应用。请参阅 _BindingExamples/Advanced/ContextExample.aspx_。

测试

采用 MVVM 架构(以及 MVP 和 MVC 实现)的关键动机之一是职责分离,这使得业务和应用程序逻辑可以放置在本质上可进行单元测试的类中。标准 WebForms 回发/代码隐藏模型的缺点已被充分记录,并导致了 WCSF 和 ASP.NET MVC 的发展。使用所提出的绑定框架和 MVVM 模式将允许相同级别的可测试性。我甚至可以争辩说,它比 WCSF/MVP 具有更大的测试覆盖率,因为几乎可以完全摆脱代码隐藏中的代码,但我不会进一步探讨这个论点,因为它们都允许编写非常可测试的代码。

MVVM 模式的使用(特别是与 DI 容器和 IOC 结合使用时)意味着您可以编写完全与应用程序其余部分解耦的 ViewModel,因此编写单元测试应该很简单。有关单元测试 View Models 的更多信息,请参阅:单元测试 View Models

随附的解决方案包含许多用于测试框架本身的测试。我对这些测试的覆盖范围不抱任何幻想,我知道它们相当有限,但我希望它们能表明框架本身以及用于利用它的代码都是以允许通过单元测试完全执行代码的方式编写的。随着这个项目的推进,扩展这个测试套件是我计划优先解决的问题。

区分大小写

在绑定过程中使用标准 `Databinder.Eval`。`Databinder.Eval` 忽略您绑定到的属性的大小写,绑定到它找到的第一个具有指定名称的属性,无论大小写。我个人不太喜欢这样,更希望通过显式匹配大小写来获得额外的控制,但为了保持一致性,在此框架的解除绑定操作中复制了这种匹配属性时不区分大小写的功能。

更改通知和级联更新

级联更新定义为:如果多个控件绑定到同一个 ViewModel 属性,并且该属性的值通过解除绑定操作进行修改,那么所有绑定控件都将使用新值进行更新。

例如:我有一个文本框和标签绑定到 ViewModel 上的 `EmployeeName` 属性。我将新名称输入到文本框中并启动回发。当页面渲染回客户端时,我也希望标签能反映新值。

为了支持级联更新,ViewModel 必须实现 `INotifyPropertyChanged` 或 `INotifyCollectionChanged`(对于 `IEnumerable`)。如果通过深层路径(通过 ViewModel 自身属性公开的对象属性)进行绑定,则底层对象也必须实现 `INotifyPropertyChanged`,并且事件必须传播到父级 (ViewModel),父级反过来必须引发 `PropertyChanged` 事件。该框架还包括一个自定义集合 `NotifyPropertyCollection`,当通过 View Model 公开 `INotifyPropertyChanged` 对象的集合时,这应该能简化开发。这不应与 `System.Collections.ObjectModel.ObservableCollection` 或 `System.Collections.Specialized.INotifyCollectionChanged` 混淆,它们用于监视集合的状态,而不是该集合项目公开的属性。

有关 `INotifyPropertyChanged` 和事件传播的更多信息,请参阅

注意:当在标记为 `serializable` 的对象上实现 `INotifyPropertyChanged` 时,您必须将 `[field: NonSerialized]` 应用于 `PropertyChanged` 事件声明,以避免 `BinaryFormatter` 尝试序列化订阅此事件的方法(及其父对象!)。例如

[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;

ViewModel 完整性

为了避免数据完整性问题,理解在解除绑定操作期间用于定位对象和属性(即原始源)的机制非常重要。绑定系统不使用键(ID)在解除绑定时将绑定解析为对象,而是依赖于在回发之间存储在 View State 中的索引路径。以下是路径示例:`AvailableAddresses[1].PhoneNumbers[3].AreaCode`。

这种方法不同于 Silverlight 和 WPF 等有状态环境使用的机制,它们可以奢侈地映射回最初检索数据的相同对象,因此可以依赖对象相等性,这是理想的情况。由于 Web 的无状态特性,我们在 ASP.NET 中没有此选项,因此我们必须存储路径(如果绑定到集合,则包括索引器),以便在解除绑定时遍历此路径,以确定将从视图检索到的值写回到何处。这种方法虽然有效,但确实引入了必须理解的责任,以避免 ViewModel 中的数据损坏。重要的是,在启动解除绑定之前,不要在回发时直接对 ViewModel 进行任何更改。如果对 ViewModel 进行更改,例如设置属性的值,则在解除绑定时此值将被覆盖。更重要的是,确保任何集合都以与初始绑定时相同的顺序呈现并包含相同数量的项目。如果您修改 ViewModel,导致集合包含的项目少于初始绑定时,您可能会收到 _索引超出范围_ 异常。如果项目的顺序不同,则由于值映射回不正确的对象,您可能会得到损坏和无效的数据。

此免责声明适用于 `Persist`(有状态)和 `Recreate`(无状态)状态模式,但无状态绑定存在更大的风险,因为重新创建 ViewModel 的责任落在页面设计者手中,而有状态绑定时,绑定器框架承担了部分责任,因为它将 ViewModel 序列化到视图状态并在每次回发时将其反序列化为对象。尽管许多人会觉得有状态绑定不值得牺牲,因为视图状态大小的考虑和并发问题的风险(在许多情况下,我会同意),但它确实最大限度地减少了此处描述的潜在问题的暴露。

关于重用 WPF 程序集的说明

我必须决定是重用 WPF 提供的类还是从头开始重新创建它们。这主要适用于 `ICommand` 和 `ObservableCollection`,但随着框架的扩展,可能会涉及其他类。重用的缺点是对 WPF 程序集的依赖。不使用它们的缺点是重复使用相同目的的对象,更重要的是涉及代码(如 Command 和 ViewModel 代码)在平台之间可移植性方面的问题。我最终决定重用,因为我没有看到任何实际的危害,尽管纯粹主义者可能会对此提出异议。

我们进展如何?

我想强调的是,提供的代码并非成品。它最多可能是 0.3 版本,但我已经到了一个阶段,在投入更多时间之前,我觉得收集一些反馈会很有用。也许(尽管我希望不是)我完全错了方向,这种方法以前没有尝试过(或分享过)是有原因的。也许有些地方您不喜欢,有些地方您喜欢。请告诉我!

除了...一些未实现的功能中列出的功能之外,还有一些领域需要进一步开发

  • 性能可以更好,目前还不明显,但一旦扩展,可能会变得很明显。我意识到框架性能的很多方面都可以改进。
  • 目前在程序化绑定时存在一个限制,最好通过示例来演示。在测试套件中有一个测试演示了这个问题:`NestedCollectionRelativePathTest`。
  • 框架的测试覆盖率需要显著扩展。
  • 错误处理也需要改进,设计目标是:在任何情况下绑定都不会抛出异常,这尚未实现,但我的借口是,在框架仍处于开发阶段时,使用异常代码更容易调试……尽管这可能是(而且很可能)一个托词。

谢谢!

感谢以下人员为我创建本文和框架时提供了有用的代码或文章

历史

  • 框架版本 0.3:首次公开发布。
© . All rights reserved.