用LINQ快速改变XML元素的值

[原文作者]:Beth Massi

[原文链接]:Quickly Changing Values of XML Elements Using LINQ

    最近我在考虑一些关于如何使用LINQ查询XML文档(或片段)中一个特定节点并且改变其值的问题(这说明人们已经开始用这种方法了,这很是让我激动)。这是一个很方便的方法:我们可以改变查询返回节点的值,这样源XML中的值也会跟着改变。

   一个例子:

   Imports <xmlns="urn:mycompany:examples:plants">

   Module Module1

    Sub Main()

        Dim plants = <?xml version="1.0" encoding="ISO-8859-1"?>

                     <CATALOG xmlns="urn:mycompany:examples:plants">

                         <PLANT>

                             <COMMON>Bloodroot</COMMON>

                             <BOTANICAL>Sanguinaria canadensis</BOTANICAL>

                             <ZONE>4</ZONE>

                             <LIGHT>Mostly Shady</LIGHT>

                             <PRICE>$2.44</PRICE>

                             <AVAILABILITY>031599</AVAILABILITY>

                         </PLANT>

                         <PLANT>

                             <COMMON>Columbine</COMMON>

                             <BOTANICAL>Aquilegia canadensis</BOTANICAL>

                             <ZONE>3</ZONE>

                             <LIGHT>Mostly Shady</LIGHT>

                             <PRICE>$9.37</PRICE>

                             <AVAILABILITY>030699</AVAILABILITY>

                         </PLANT>

                         <PLANT>

                             <COMMON>Marsh Marigold</COMMON>

                             <BOTANICAL>Caltha palustris</BOTANICAL>

                             <ZONE>4</ZONE>

                             <LIGHT>Mostly Sunny</LIGHT>

                             <PRICE>$6.81</PRICE>

                             <AVAILABILITY>051799</AVAILABILITY>

                         </PLANT>

                     </CATALOG>

 

        Dim q = From plant In plants…<PLANT> _

                Where plant.<COMMON>.Value = "Columbine" _

                Select plant

 

        For Each item In q

            q.<PRICE>.Value = "$49.99"

            q.<LIGHT>.Value = "Full Sun"

        Next

 

        plants.Save("plants.xml")

    

    End Sub

 

 End Module

    注意两点:记得导入XML用到的所有命名空间,否则查询会返回空值;如果导入了构架(这个很容易就能办到,请参考这里)记得用XML智能提示。当然,我们也可以不把XML的内容全部写在代码里,而是从文件或者通过URI加载,这样可以得到一样的结果:

    Dim plants = XDocument.Load("plants.xml")

    Dim q = From plant In plants…<PLANT> _

        Where plant.<COMMON>.Value = "Columbine" _

        Select plant

 

    For Each item In q

    q.<PRICE>.Value = "$49.99"

    q.<LIGHT>.Value = "Full Sun"

    Next

 

    plants.Save("plants.xml")

   在这个例子中,我们用新的值覆盖源文档plants.xml。上文的两个例子产生同样的结果:

   <?xmlversion="1.0"encoding="iso-8859-1"?>

   <CATALOGxmlns="urn:mycompany:examples:plants">

    <PLANT>

    <COMMON>Bloodroot</COMMON>

    <BOTANICAL>Sanguinaria canadensis</BOTANICAL>

    <ZONE>4</ZONE>

    <LIGHT>Mostly Shady</LIGHT>

    <PRICE>$2.44</PRICE>

    <AVAILABILITY>031599</AVAILABILITY>

 </PLANT>

 <PLANT>

    <COMMON>Columbine</COMMON>

    <BOTANICAL>Aquilegia canadensis</BOTANICAL>

    <ZONE>3</ZONE>

    <LIGHT>Full Sun</LIGHT>

    <PRICE>$49.99</PRICE>

    <AVAILABILITY>030699</AVAILABILITY>

 </PLANT>

 <PLANT>

    <COMMON>Marsh Marigold</COMMON>

    <BOTANICAL>Caltha palustris</BOTANICAL>

    <ZONE>4</ZONE>

    <LIGHT>Mostly Sunny</LIGHT>

    <PRICE>$6.81</PRICE>

    <AVAILABILITY>051799</AVAILABILITY>

 </PLANT>

</CATALOG>

 

 

 

 

基于WPF LINQ的动态数据模型

[原文作者]:Beth Massi

[原文链接]:Dynamic Data Entry with WPF and LINQ

   上一节我讲到用XML编写动态WPF UI, 尽管这里的UI是动态生成的,但仍有一处不尽人意的地方,就是我们采用的是一个具体的对象customer(来源于LINQ to SQL classes)。 如果想要我们的应用程序既能够动态生成WPF UI 又能动态地编辑处理数据库里的任何表数据,就需要进一步参数化程序代码 ——而不仅限于customer。 这样只需要修改数据库表的定义而不用更新对象模块和重新编译代码。

   实现方法是试图在运行时加载,处理一个简单的无类型的(或者称作通用类型)DataTable,并运用XML literals特性 –为SqlDataAdapter生成SELECT和UPDATE语句。要注意一件事情,这个过程必须依赖数据库验证规则。因此这种方式只能用于处理非常简单的表(缩略语.维护表)。

 
   我需要创建一个强类型(指定类型)的DataTable包含表的定义,用来取代LINQ to SQL模板。这个datatable存储了字段的元数据(字段名,类型,等等)以便我们对表的操作。右键点击项目工程,添加新模板,选择DataSet template。我将它命名为TableSchemaDataSet,然后拖拽Server Explorer-〉Data Connection –〉Northwind Database下的存储过程GetTableSchema(这个已经在前面章节中生成好了)到DataSet 设计器上,自动为我们生成所需的DataTable,重命名为TableSchema,并保存。

 
   接下来精彩的部分。因为我们并不想知道当前表的定义,我们会加载一个通用类型的DataTable生成一个动态的WPF 窗口。DataTable和DataSet都建好了,还有两个步骤需要手动设置。

   首先定义一个公共属性(Public Property)存放表名,同时设置默认值为“Shippers”,然后创建一批私有变量引用所需的ADO.NET对象。

Imports <xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

Imports <xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

Imports System.Windows.Markup

Imports System.Data.SqlClient

Imports System.Data

 

Partial Public Class Window2

    ‘This is the metadata table we created in the DataSet Designer

    Private TableSchema As New TableSchemaDataSet.TableSchemaDataTable

    ‘ADO.NET objects used to load and save the table we’re editing

    Private TableDataAdapter As New SqlDataAdapter

    Private TableConnection As New SqlConnection(My.Settings.NorthwindConnectionString)

    Private Table As DataTable

    ‘This is the key field used in searching for a row in this example

    Private PKField As TableSchemaDataSet.TableSchemaRow

 

    ‘This property can be set before the Form.Show() to edit any table

    Private m_tableName As String = "Shippers"

    Public Property TableName() As String

        Get

            Return m_tableName

        End Get

        Set(ByVal value As String)

            m_tableName = value

        End Set

    End Property

 


    现在UI定义好了,然后设置主键字段(TableSchemaDataRow 对象)以便我们点击Find按钮时运用到Update和Select操作。

Private Sub Window1_Loaded() Handles MyBase.Loaded

    Try

        ‘Get the schema of the database table we want to edit

        Dim taSchema As New TableSchemaDataSetTableAdapters.TableSchemaTableAdapter

        taSchema.Fill(Me.TableSchema, Me.TableName)

 

        ‘Create the DataTable that will hold the record we’re editing

        Me.Table = New DataTable(Me.TableName)

        Me.Title = Me.TableName

        Me.LoadUI()

        Me.SetPrimaryKey()

        Me.SetUpdateCommand()        

    Catch ex As Exception
        MsgBox(ex.ToString)
        Me.Close()
    End Try
End Sub
 
Private Sub LoadUI()
 
    Dim UI = <Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 Name="Grid1">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="100*"/>
                     <ColumnDefinition Width="200*"/>
                 </Grid.ColumnDefinitions>
                 <StackPanel Name="StackLabels" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select <Label
                                    Height="28"
                                    Name=<%= column.ColumnName & "Label" %>
                                    HorizontalContentAlignment="Right">
                                    <%= column.ColumnName %>:</Label> %>
                 </StackPanel>
                 <StackPanel Grid.Column="1" Name="StackFields" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select GetUIElement(column) %>
                 </StackPanel>
             </Grid>

     Me.DynamicContent.Content = XamlReader.Load(UI.CreateReader())
 
End Sub
 
Private Function GetUIElement(ByVal columnInfo As TableSchemaDataSet.TableSchemaRow) As XElement
    Select Case columnInfo.DataType.ToLower
        Case "datetime", "int", "smallint", "money"
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
        Case "bit"
            Return <CheckBox
                       HorizontalContentAlignment="Left"
                       Name=<%= "chk" & columnInfo.ColumnName %>
                       IsChecked=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>>
                       <%= columnInfo.ColumnName %>
                   </CheckBox>
        Case Else
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       MaxLength=<%= columnInfo.MaxLength %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
    End Select
End Function

 

Private Sub SetPrimaryKey()    这样我们就可以通过设置TableName,动态生成UI,通过Find找到想要的数据,并可以修改保存数据。而且可以不用重新编译修改数据库表结构。
 

    ‘Grab the Primary Key column of the table we want to edit so we can use it in the search

    Me.PKField = (From column In Me.TableSchema Where column.IsPrimaryKey = 1).FirstOrDefault()

End Sub

Private Sub btnFind_Click() Handles btnFind.Click

    If Me.txtSearch.Text <> "" Then

        Try

            ‘Create the SELECT command

            Dim cmdText = <s>

                          SELECT * FROM <%= Me.TableName %>

                          WHERE <%= Me.PKField.ColumnName %> =

                                <%= If(Me.PKField.DataType.Contains("char"), _

                                    "’" & Me.txtSearch.Text & "’", _

                                    Me.txtSearch.Text) %>

                          </s>.Value

 

            Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

            Me.Table.Clear()

            Me.TableDataAdapter.SelectCommand = cmd

            Me.TableDataAdapter.Fill(Me.Table)

 

            Me.DataContext = Me.Table

            Dim view = CollectionViewSource.GetDefaultView(Me.Table)

            view.MoveCurrentToFirst()

 

        Catch ex As Exception

            MsgBox(ex.ToString)

            Me.DataContext = Nothing

        End Try

    Else

        Me.DataContext = Nothing

    End If

End Sub

 

Private Sub SetUpdateCommand()

    ‘Set the UpdateCommand so that we can save edited records in the table

    Dim cmdText = <s>

                  UPDATE <%= Me.TableName %>

                  SET <%= From column In Me.TableSchema _

                          Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _

                          Select <c>

                                     <%= column.ColumnName %> = @<%= column.ColumnName %>

                                     <%= If(Me.TableSchema.Rows.IndexOf(column) < _

                                            Me.TableSchema.Rows.Count – 1, ", ", "") %>

                                 </c>.Value %>

                  WHERE <%= Me.PKField.ColumnName %> = @<%= Me.PKField.ColumnName %>

                        <%= From column In Me.TableSchema _

                            Where column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" _

                            Select <c>

                                     AND <%= column.ColumnName %> = @<%= column.ColumnName %>

                                   </c>.Value %>

                  </s>.Value

 

    Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

    Dim p As SqlParameter

 

    For Each column In Me.TableSchema

        If column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" Then

            ‘Note: It’s recommended to use a TimeStamp column in your tables for concurrency checking

            p = New SqlParameter("@" & column.ColumnName, SqlDbType.Timestamp)

            p.SourceVersion = DataRowVersion.Original

            p.SourceColumn = column.ColumnName

            cmd.Parameters.Add(p)

        Else

            p = New SqlParameter("@" & column.ColumnName, _

                                 CType([Enum].Parse(GetType(SqlDbType), column.DataType, True), SqlDbType))

            p.SourceColumn = column.ColumnName

            p.SourceVersion = DataRowVersion.Current

            cmd.Parameters.Add(p)

        End If

    Next

 

    Me.TableDataAdapter.UpdateCommand = cmd

End Sub

 

    然后我们可以在Loaded事件处理函数中编写逻辑代码,加载元数据,创建和加载XAML显示UI,再然后就是处理TableDataAdaper上的UpdateCommand。

使用Open XML Diff工具比较两个开发XML包

[原文作者]:Beth Massi

[原文链接]:Comparing Two Open XML Packages with Open XML Diff

    昨天我从微软的一员工那里收到一封email,内容是关于体验一下他正在设计的一个工具Open XML Diff。 他的名字叫Pranav Wagh。 他也发布了这个工具的一个版本,可以从他的博客上看到。


   当你准备写代码去生成一个开放XML文档,而且当你不确定要写的XML语句是什么的时候,你可以使用Open XML Diff。也就是说你知道你想要的文档在Word中是如何呈现的,但是不是很明白怎样去设置某个元素或者属性。你可以保存一个文档的备份,修改并且保存它,然后用此工具比较前后两个文件,就能使你看出你所需要的XML语法。
我在我的Vista机器上下载了源码,但是遇到了很多信任问题,工程需要找一个叫xmldiffpatch.dll的文件,而说明文档说从这里来安装,但是这样安装没有将程序集放在C:\Program Files\目录下, 在vista系统中这样引用程序集是不行的。 我发现Pranav在OpenXMLDiff\bin\Realease文件夹中已经提供了xmldiffpatch.dll。所以我为那三个工程创建了一个解决方案,并且设置两个工程间的引用,一个是OpenXMLComparer Winforms client到OpenXMLDiff library,另一个是OpenXMLDiff library到ViewRenderer。 现在你需要添加的唯一的二进制文件引 用就是XMLdiffPatch。最后设置工程OpenXMLComparer作为启动项,编译整个解决方案。


   为了测试这个工具,我以上次发布的Word文档“MyDocument.docx”作为对象,这个文档只包含文本“This is my document”。对它我保存了一个副本文件叫做“MyDocument1.docx”,并且做了一点小的改动:我降所有的句子字体加粗。现在我用工具比较这两个文档。报告发现包中三个xml文件改变了。其中最重要的是MainDocument部分,文件是document.xml。下面是报告中的部分快照,从中可以看出文件的区别:

 

   相当完美!这个确实帮助我理解通过office修改开放XML包文件,哪些文件的改变起了作用。需要了解更多请阅读Office 设计文档。


   务必在Open XML Diff站点上给出你的反馈。

 

质量控制的里程碑 & 狗食

[原文作者]:Matt Gertz

[原文链接]:Milestone Quality & Dogfooding

    在辛苦又有成果的工作后,我们完成了质量控制的里程碑。这个里程碑的目的是让我们准备好开发下一版本的Visual Studio,Team System.NET。虽然这些开发不是专门针对Visual Basic的,我还是选择利用这个“天字第一号讲坛”让大家知道这些努力。Visual Basic连同Visual Studio.NET产品都是会受到影响的。

   像在我前面的帖子中提到的,质量控制里程碑的工作不会包括新的特性和类似这样的事情,它的主要工作是准备工具和程序,好让我们可以开始下一个周期的工作。在开始编码之前这个是很重要的。因为在工程进度的中间做改变,是非常具有破坏性的。质量控制的里程碑的工作对于我们来说也是非常有挑战性的,而我很高兴能看到我们在设计上做的很多改变都没有造成什么问题。

   在之前的帖子里面讲到过,我们决定把大部份质量控制里程碑的工作集中在“dogfooding”上面—用我们自己的代码去完成开发工作,当然质量控制里程碑的工作也会包括其他一些准备工作。在这篇文章里面我主要讲"dogfooding”。因为三个原因我们会"dogfood"我们的代码。

1. 为用户增加产品的稳定性:在产品发布给用户之前,我们以每天使用的方式来找缺陷。
2. 实践我们所倡导的:如果我们觉得某个特性用户使用起来比较好,那么我们也应该会使用   它。
3. 这是做事情的正确方法: 我们坚信我们为产品开发提供的是最好的基础,这些产品包括从业余爱好写的代码到企业的解决方案。开发Visual Studio最好的工具就是Visual Studio

   现在我们对dogfooding不再陌生了,但是我们从来没有那么早开始做dogfooding。我们会在代码大部分完成的时候,在Beta1Beta2阶段做dogfooding。在早期的版本中,(例如重写了所有东西的Visual Studio .NET 2002),这样做就是比较有效的,因为那个时候代码没有集合在一起。另一方面,在后期去做dogfooding没有给我们带来相同的覆盖率,因为在Beta阶段,我们会集中精力去解决缺陷而不是开发新的特性,开发有质量的特性是VSTS,和.NET 的重点。现在我们有稳定的代码平台,这样我们就可以调整早些去做dogfooding,早些找到开发缺陷,在还容易做改动的时候就对此特性做改进。我非常兴奋可以做到这点,我一直都羡慕Windows和Office团队的能力可以在早期就做dogfooding

 

Team Foundation Server (TFS)

   在早期的产品开发周期中,我们用专门的系统去跟踪缺陷,另外一个系统存储代码,还有缺陷报告和报表去跟踪特性的进度。这三者彼此没有进行任何交流。一个状态报告也要比它应该有的复杂得多。一个我们要求的基于代码大小和管理生命周期的系统一直都是我们的目标,现在我们可以转向我们自己的产品了,Team Foundation Server(TFS),一个可以处理这三个方面的产品(还有很多其他方面)。

   尽管对这个改变有很多支持的声音,但诚实的说,在我们转向TFS的时候,还是很紧张不安的。Visual Studio, Team System.NET组成了一个相当庞大的部门。如果只看开发者部门不考虑微软其余的部门,都已经可以成为世界上最大的企业级的软件销售商之一,进入前五名是很容易的。我们长期来都以精确的方式做事情,因为很多的程序也要随之一起改变,所以从效率的角度,我们也很难放弃。

   幸运的是,现在已经不必担心了。可以肯定的是,在VS2008开发时,我们25%的产品已经率先使用了TFS,在体验中,也做了相应的改进。正因为他们努力的dogfooding,其余的部门在几个月之后可以很容易的转向TFS—我们仅在几天内就把所有的源代码移进新的项目并且编译成功,那可是很多的代码。在做的同时,我们也进行了一些改进,使开发者们可以更好的和存贮的代码交互—这个改进最终也会使我们的用户受益。

   此外,我们也用TFS跟踪缺陷和特性,将Team Explorer作为Visual Studio的家族产品补充进去。这是另外一个先驱者们在前面的VS版本做dogfooding的例子。所以这最后的转变也是同样的顺利。最后的结果就是现在我们用报告服务可以连接代码,特性计划和缺陷跟踪。这样,在管理下个产品生命周期上面的节约是显著的。我非常兴奋现在正在使用TFS

Side-by-side (SxS)

   当然,如果我们计划用产品本身去开发下个产品,我们必须能够像客户一样安装产品,还不能干扰可能还存在于我们机器上的前面版本的产品。这个目标被叫做"side by side”或者简称为"SxS”。这里面包括了很多版本控制,改变注册表信息,解决一些必须的硬编码程序集引用等等,这会跟随着很多很多测试工作。在产品生命周期中,很多组件都需要改变,这些组件来自不同的团队。在过去,这些事情会到beta里程碑的时候才完成,因为很多人在beta之前都在非常努力的写特性,他们所有的时间都花在这个上面了。但是,推迟SxS的工作并不是我们希望这样做的—这意味着,用户安装beta版本的产品时,有个问题会一直存在,那就是在卸载beta后,会要求修复到之前的产品。此外,我们在早期的产品生命周期中添加特性,同时也造成了更多的SxS副作用。

   要避免这些缺陷,我们决定在MQ里面来解决这个问题。坦白说,要这么快的做好这件事是很有挑战性的—花掉了我们所有人很多个夜晚和周末的时间,为了加快工作我们陷入很多需要重组的程序中。但是,在我们开始为下一版本开发特性之前,花额外的时间来更早的完成SxS的工作,这不仅使我们可以在不破坏VS2008安装(有可能我们已经安装了VS2008)的情况下dogfood我们的产品,而且用户的beta安装和卸载体验也会有更高的质量,这会使用户有更好 的beta体验,我们也会得到更好的回馈。

 

 

在WPF中显示 验证数据有效性信息

[原文作者]:Beth Massi

[原文链接]:Displaying Data Validation Messages in WPF

  

    

     你可能从我之前的那几篇文章中看出,我最近在研究WPF中的不同的数据案例。昨天我在摆弄WPF.net 3.5中的数据有效性验证,而WPF.net 3.5一起工作的非常好。在这篇文章中,我将从头到尾过一遍如何通过使用IDataErrorInfo 接口来触发你自己的数据对象中有效性验证,然后再介绍一些你能用来显示给用户的有效性验证错误信息的验证错误信息模板。
     在数据对象中应用有效性验证
 
    如果你使用自定义业务逻辑对象或者LINQ to SQL类,你首先需要实现IDataErrorInfo接口来收集你的对象上的有效性验证信息.如果你在WPF或者Windows Forms中使用DataSets, DataRowView已经实现了这个接口,所以你只需加有效性验证到你的DataTable Patial类就足够了.打开DataSet 设计器,右击DataTable, 选择”View Code” 就可以了. 比如,如果你有个customer数据表我们能像下面这样来验证LastName 字段的有效性:
    Partial Class CustomerDataSet
    Partial Class CustomerDataTable
 
        Private Sub CheckLastName(ByVal row As CustomerRow)
            If row.IsNull("LastName") OrElse row.LastName = "" Then
                row.SetColumnError(Me.LastNameColumn, "Please enter a last name")
            Else
row.SetColumnError(Me.LastNameColumn, "")
            End If
        End Sub
 
        Private Sub CustomerDataTable_ColumnChanged(ByVal sender As Object, _
                                                    ByVal e As System.Data.DataColumnChangeEventArgs) _
                                                    Handles Me.ColumnChanged
            If e.Column Is Me.LastNameColumn Then
                Me.CheckLastName(CType(e.Row, CustomerRow))
            End If
        End Sub
 
 
        Private Sub CustomerDataTable_TableNewRow(ByVal sender As Object, _
                                                  ByVal e As System.Data.DataTableNewRowEventArgs) _
                                                  Handles Me.TableNewRow
            Dim row As CustomerRow = CType(e.Row, CustomerRow)
            ‘This will fire the ColumnChanged event which will give
            ‘immediate feedback to user when a row is added.
            ‘(Stick other default values here too.)
            row.LastName = ""
        End Sub
    End Class
 
End Class
 
    如果你是创建自己的自定义业务逻辑对象或者使用LINQ to SQL 类,那么你需要自己实现IDataErrorInfo接口。我示范过如何在LINQ to SQL类实现这个的,当时是在一个业务逻辑基类中实现的。这里有个精简版的对一个customer Linq to SQL 类的实现示例,同样是验证LastName字段的有效性:
Partial Class Customer
    Implements System.ComponentModel.IDataErrorInfo
 
    ‘This dictionary contains a list of our validation errors for each field
    Private validationErrors As New Dictionary(Of String, String)
 
    Protected Sub AddError(ByVal columnName As String, ByVal msg As String)
        If Not validationErrors.ContainsKey(columnName) Then
            validationErrors.Add(columnName, msg)
        End If
    End Sub
 
    Protected Sub RemoveError(ByVal columnName As String)
        If validationErrors.ContainsKey(columnName) Then
            validationErrors.Remove(columnName)
        End If
    End Sub
 
    Public Overridable ReadOnly Property HasErrors() As Boolean
        Get
            Return (validationErrors.Count > 0)
        End Get
    End Property
 
    Public ReadOnly Property [Error]() As String _
        Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            If validationErrors.Count > 0 Then
                Return String.Format("{0} data is invalid.", TypeName(Me))
            Else
                Return Nothing
            End If
        End Get
    End Property
 
    Default Public ReadOnly Property Item(ByVal columnName As String) As String _
        Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            If validationErrors.ContainsKey(columnName) Then
                Return validationErrors(columnName).ToString
            Else
                Return Nothing
            End If
        End Get
    End Property
 
    Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction)
        Me.CheckLastName(Me.LastName)
 
        If Me.HasErrors Then
            Throw New Exception(Me.Error)
        End If
    End Sub
 
    Private Sub OnLastNameChanging(ByVal value As String)
        Me.CheckLastName(value)
    End Sub
 
    Private Sub CheckLastName(ByVal value As String)
        If value = "" Then
            Me.AddError("LastName", "Please enter a last name")
        Else
            Me.RemoveError("LastName")
        End If
    End Sub
   
End Class
 
WPF中的数据绑定
 
   既然我们的数据对象能自己验证有效性,我们就能把它们绑定到一个Form上了。一旦你掌握了记住语法的窍门,在XAML中建立一个简单的包含一些TextBoxWPF窗口并绑定它们,是非常简单的。关键是确保你指定BindingValidateOnDataErrors属性并设置为True. 看看下面的XAML中的TextBox元素:
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Customers" Height="253" Width="300" Name="Window1">
    <Grid Margin="6">
        <Grid.RowDefinitions>
            <RowDefinition Height="222*" />
            <RowDefinition Height="40*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="112*" />
            <ColumnDefinition Width="166*" />
        </Grid.ColumnDefinitions>
        <StackPanel Name="StackPanel1">
            <Label Name="Label1"
                   Width="Auto"
                   HorizontalContentAlignment="Right"
                   Margin="3">
                Last Name:</Label>
            <Label Name="Label2"
                   Width="Auto"
                   HorizontalContentAlignment="Right"
                   Margin="3">
                First Name:</Label>
            <Label Name="Label3"
                   Width="Auto"
                   HorizontalContentAlignment="Right"
                   Margin="3">
                City:</Label>
            <Label Name="Label4"
                   Width="Auto"
                   HorizontalContentAlignment="Right"
                   Margin="3">
                State:</Label>
            <Label Name="Label5"
                   Width="Auto"
                   HorizontalContentAlignment="Right"
                   Margin="3">
                ZIP:</Label>
        </StackPanel>
        <StackPanel Grid.Column="1" Name="StackPanel2">
            <TextBox
                Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"
                Name="TextBox1"
                Height="28"
                Width="Auto"
                HorizontalContentAlignment="Left"
                Margin="3" />
            <TextBox
                Text="{Binding Path=FirstName, ValidatesOnDataErrors=True}"
                Name="TextBox2"
                Height="28"
                Width="Auto"
                HorizontalContentAlignment="Left"
                Margin="3" />
            <TextBox
                Text="{Binding Path=City, ValidatesOnDataErrors=True}"
                Name="TextBox3"
                Height="28"
                Width="Auto"
                HorizontalContentAlignment="Left"
                Margin="3"/>
            <TextBox
                Text="{Binding Path=State, ValidatesOnDataErrors=True}"
                Name="TextBox4"
                Height="28"
                Width="Auto"
                HorizontalContentAlignment="Left"
                Margin="3"/>
            <TextBox
                Text="{Binding Path=ZIP, ValidatesOnDataErrors=True}"
                Name="TextBox5"
                Height="28"
                Width="Auto"
                HorizontalContentAlignment="Left"
                Margin="3" />
        </StackPanel>
        <Button Name="btnAdd"
                Grid.Column="1" Grid.Row="1"
                Margin="0,0,79,6"
                Height="24" Width="75"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Right" >Add</Button>
        <Button Name="btnSave"
                Grid.Column="1" Grid.Row="1"
                HorizontalAlignment="Right"
                Margin="0,0,0,6"
                Width="75" Height="24"
                VerticalAlignment="Bottom">Save</Button>
    </Grid>
</Window>
 
 
    现在我们能载入我们的数据,在Window_Loaded事件处理函数中赋给Window.DataContext. 如果你使用DataSet,那么像平常一样建立TableAdapter Query并填充数据。然后将Customer数据表赋给窗体的DataContext:
Me.CustomerTableAdapter.Fill(Me.MyCustomerData.Customer)
Me.CustomerTableAdater.Fill(Me.MyCustomerData.Customer)
 
 
 
如果你使用LINQ to SQL类,那么只要调用SQL DataContext来加载你的customer列表:
Dim db As New MyDatabaseDataContext
Me.DataContext = From Customer In db.Customers _
                 Where Customer.LastName = "Massi"
 
WPF的默认有效性验证错误模
 
   如果我们就这样执行的话,当我们的有效性验证失败的时候WPF会给我们一个默认的可见的线索. 控件边框将绘制为红色显示这里存在着问题,然而并没有错误信息显示. 对啊,这还真有帮助,你就等着你的技术支持热线的电话铃声响起吧,亲爱的。
 

 

设置自定义有效性验证Style
   我们显然希望能让用户知道这里的问题怎么解决.让我们来做一些简单的例子在ToolTip中来显示错误信息. 现在我们能在Window.Resources部分中创建一个Style,然后应用于这个Form中的TextBox. 这个 Style创建一个触发器,Validation.HasError变成True的时候 ,用来设置ToolTip属性为有效性验证的错误信息 .
<Window.Resources>
    <Style TargetType="TextBox">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
 
 
   现在当我们再次运行这个程序,当鼠标置于TextBox上的时候你能看到显示在ToolTip里的有效性验证信息. 这比之前的好了些 ! 但是这个办法只适用于TextBox. 那像 checkBox,ComboBox等这样的控件又怎么办呢 ? 我们需要把这些都声明在一个适应于整个应用程序的地方 .没问题,我们可以把这种Style贴在Application.Resources. 我们也能指定 TargetType=”Control”,这样我们就能定义另外的适合基于这种Style的其他控件的Style.打开Application.xaml,将它们加入Resources部分:
 
<Application.Resources>
    <Style TargetType="Control" x:Key="myErrorTemplate">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="TextBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="CheckBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="ComboBox" BasedOn="{StaticResource myErrorTemplate}" />
</Application.Resources>
    我们只需指定模板的x:Key,然后就能在继承的Style上设置BasedOn属性了 . 现在整个应用程序的控件都能应用这种Style了。
    替换整个的错误模板
    到目前为止,我们所做的是指定一个Style触发器了. 默认的WPF错误模板仍然在使用中因为我们还是看到控件的红边框. 这里我们能在Applicaion.Resource定义一个新的错误模板来完全的改变这个错误模板.让我们来通过一个简单的示例来示范创立一个在控件上显示一般错误信息的错误模板.Style 中的Trigger上面的部分(我们会把ToolTip信息放置于那儿),我们设置Validation.ErrorTemplate属性的值为我们自己的控件模板。
 
<Style TargetType="Control" x:Key="myErrorTemplate">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <TextBlock Foreground="Red" Text="DOH! Thank you for trying."/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>
      现在当我们再次运行,我们仍然可以看到我们的ToolTip当我们将鼠标放在控件上的时候.但是我们将在我们自己的控件模板中的TextBlock覆盖了原来的控件,注意:现在已经没有红色的边框了:

 

 

    好的, 我承认这是一个蹩脚的示例,.问题(除了这讽刺的消息)TextBlock确实覆盖了那控件,你必须将鼠标置于边缘来显示ToolTip. 另外一个问题当然是如果我们开始再次输入字段值,直到我们输入完毕之后那个信息才会消失,这是很不方便的.
   你可以插入一个DockPanel到控件模板里面,然后把TextBlock停靠在右边以在控件后面显示文本 (这里让我们只显示一个星号),如果说你仍然想有红色边框在控件周围,我们可以在我们的XAML中为错误模板的Setter.Value设置一个叫AdornedElementPlaceholder的特别元素:
<Setter Property="Validation.ErrorTemplate">
    <Setter.Value>
        <ControlTemplate>
            <DockPanel LastChildFill="True">
                <TextBlock DockPanel.Dock="Right"
                        Foreground="Red"
                        FontSize="11pt"
                        FontWeight="Bold">*
                </TextBlock>
                <Border BorderBrush="Red" BorderThickness="1">
                    <AdornedElementPlaceholder Name="myControl"/>
                </Border>
            </DockPanel>
        </ControlTemplate>
    </Setter.Value>
</Setter>
 

 

   

     在WPF中用.net framework 3.5来验证你的数据对象的有效性跟之前用IdataErrorInfo接口的Winform是一样的.然而,WPF式样和控件模板可以非常方便的显示可视线索给用户.如果你能发挥想象,你就可以用WPF来做.    

     享受这一切吧!