随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

WPF在SP1中更好的数据编辑功能

[原文作者]:Beth Massi    
[原文链接]:Better Data Editing Features in WPF with SP1       
 
 
   当我开始钻研数据绑定时,在WPF中我最想念的一项功能,是在BindingListCollectionView和我所喜欢的winforms BindingSource之间的一致性功能。BindingListCollectionView就像在winforms下的资源捆绑一样,提供了限制收集数据(或数据表)的导航、流动、过滤和排序。
   然而在合集中并不支持处理添加和删除项目。你可以从我的WPF窗体数据录像中注意到,当我从数据中添加或者删除一行时,我只能直接进入数据表进行操作。当我用数据表工作时这不会有问题,因为他们可以做他们自己的处理编辑(连同修改记录)。不过,为了有良好的客户业务集合,实现典型的绑定接口,这通常是一个必要的功能
SP1在WPF's BindingListCollectionView中增加了新的属性和方法
   发布的Visual Studio/.NET FX SP1已经增强了BindingListCollectionView,包括新的属性和方法:CanAddNew 属性, CanCancelEdit 属性, CanRemove属性, CurrentAddItem属性, CurrentEditItem属性, IsAddingNew属性, IsEditingItem属性, ItemProperties属性, NewItemPlaceholderPosition属性, AddNew方法, CancelEdit 方法, CancelNew 方法, CommitEdit 方法, CommitlNew 方法, EditItem 方法, Remove 方法, RemoveAt 方法.
   有一件事情我想在这里指出,不同的是,在Winforms中,无论是修改或增加数据源(也就是数据表或集合),我们都习惯于访问BindingSource上的EndEdit来实现处理变换。在WPF中,有一个CommitEditCommitNew的单独访问,如果在增加的时候调用了AddNew,你必须要确认你不是在调用CommitEdit,否则你就只能调用CommitNew。我不知道他们为什么把这两者区分开来。在使用DataSets时,我总是几乎立刻提交处理(如在填写默认值后),并且可以通过Accept/RejectChanges方法来使用DataSet的修改记录。
 
用新的AddNew和Remove Methods来增加或删除数据
   当我们想在数据表中增加一行新的数据,我们现在可以直接调用BindingListCollectionView中的AddNew。例如:
Private OrderData As New OrdersDataSet
Private OrdersViewSource As BindingListCollectionView
Sub New()
    ' This call is required by the Windows Form Designer.
    InitializeComponent()
 
    ' Add any initialization after the InitializeComponent() call.
    Me.LoadData()
    Me.DataContext = Me.OrderData.Orders
    Me.OrdersViewSource = CollectionViewSource.GetDefaultView(Me.DataContext)
End Sub
Private Sub AddNewOrder()
    '--- Old Code ---
    'Add a new row to the collection
    'Dim order = Me.OrderData.Orders.NewOrdersRow
    'Me.OrderData.Orders.AddOrdersRow(order)
    'Up to us to update the position
    'Me.OrdersViewSource.MoveCurrentToLast()
 
    '--- New Code ---
    'Add a new row to the collection
    Me.OrdersViewSource.AddNew()
    'Push changes into the DataTable
    Me.OrdersViewSource.CommitNew()
End Sub
 
 
   首先我通过DataSetpartial类来处理所有的验证和设置的默认值,因此如果我们工作于自己的商业对象集合时,AddNewOrder()方法仍将保持不变。这就是为什么我习惯于用Winforms,因为你可以轻松地交换数据源,又不会弄乱数据绑定代码。不过需要指出的是,如果CommitNewDataRow无法被调用,你再写入DataSet时就会报错。所以一定要确保调用在数据表partial类中的TableNewRow事件,并且写入有效的默认值。
删除当前行也简单:
Private Sub RemoveOrder()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        '--- Old Code ---
        'Dim order As OrdersDataSet.OrdersRow
        'order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, OrdersDataSet.OrdersRow)
        'order.Delete()
 
        '--- New Code ---
        Me.OrdersViewSource.Remove(Me.OrdersViewSource.CurrentItem)
    End If
End Sub 
 
WPF中使用Master-Detail窗体
 
   这些都是很好的改进,但是由于BindingSource是一个可视化的组件,并且能够处理CurrencyManager 目前Winforms中的BindingSource仍然比IMHO方便。当你在WPF中用master-detail绑定是一件痛苦的事情。你必须获得一个BindingListCollectionView的详细引用,每次detail view有变动,都需要再次引用(这将是ListBox或者ListView上的一个ItemSource)。如果你在XAML中能正确地建立绑定,那么当父模块变换时detail view就会自动变换。然而,如果你想在子模块中用AddNew,你就需要实时在每次都获得一个代码的引用,因为视图是动态的。Winforms 绑定源处理这种情况更好些。
   尽管我们需要每次都获得引用,这也是相当简单的:
Private Sub AddNewDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        '--- Old Code ---
        'Dim order As OrdersDataSet.OrdersRow
        'order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, _
        '              OrdersDataSet.OrdersRow)
        'Dim detail = Me.OrderData.OrderDetail.NewOrderDetailRow
        'detail.OrderID = order.OrderID
        'Me.OrderData.OrderDetail.AddOrderDetailRow(detail)
 
        '--- New Code ---
        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)
        detailView.AddNew()
        'Note that the related OrderID is set for us automatically just like Winforms
        detailView.CommitNew()
    End If
End Sub
 
Private Sub RemoveDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
 
        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)
 
        If detailView.CurrentPosition > -1 Then
            '--- Old Code ---
            'Dim detail As OrdersDataSet.OrderDetailRow
            'detail = CType(CType(Me.OrderDetailsViewSource.CurrentItem, DataRowView).Row, _
            '               OrdersDataSet.OrderDetailRow)
            'detail.Delete()
 
            '--- New Code ---
            detailView.Remove(detailView.CurrentItem)
        End If
    End If
End Sub
 
   通过使用新的AddNewRemove方法似乎也能解决一些问题,我曾经遇到过使用LINQ to SQL产生子模块集合(EntitySets),所以最好开始运用这些新方法的优势。在以后的博客帖子中,我会用我们所做的LINQ to SQL N-Tier application,在末尾补上WPF Front
   我可能不会更新我们已经做的工作的视频,但以后我会在适当的时候使用这些新的方法和属性,所以请下载SP1
Enjoy!

posted on 2008-10-20 10:22:29 by VBCTI  评论(0) 阅读(2907)

用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>

 

 

 

 

posted on 2008-09-27 16:41:40 by VBCTI  评论(0) 阅读(3063)

基于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。

posted on 2008-09-22 17:05:39 by VBCTI  评论(0) 阅读(3495)

使用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站点上给出你的反馈。

 

posted on 2008-09-19 17:42:15 by VBCTI  评论(0) 阅读(3363)

在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来做.    

     享受这一切吧!
 
 

 

 

   

posted on 2008-09-17 17:07:12 by VBCTI  评论(0) 阅读(2206)

使用Visual Studio插件浏览Office2007 文档

[原文链接]:Handy Visual Studio Add-In to View Office 2007 Files

[原文作者]:Beth Massi

      上个月我在Redmond的时候向一个同事提起过我真的非常欣赏Office 2007正在应用的Open XML的格式Opem XML 是继如doc、.dot、.xls 和 .ppt等二进制 文件后一种全新的office文件格式)和我是如何应用这种格式及”LINQ toXML” 的.他也向我介绍了VSTO Power Tools,这个工具包含了称作Open XML Package Editor 的Visual Studio 插件。 这个工具可以让用户通过简洁小巧的树状菜单在Word,PowerPoint和Excel中浏览Office2007 文档,这可以使得用户操纵Open XML 文件和XML中的其它各个部分。在安装了VSTO Power Tools 后, 只要从Visual Studio项目中直接双击Office 2007 文件打开这个工具就可以使用了。

<来自VSTO Power Tool文档>
      打开XML Package Editor
      这是一个Visual Studio2008插件,它允许分析和编辑Open Packaging Conventions的文件, 包括Word, Excel and PowerPoint 文档。它的特性如下:
  • 在Visual Studio里直接打开Office 2007 Open XML Package 文件或者 XPS Package 文件。 (Office XML 格式是针对 Office Word 2007、Office Excel 2007 和 Office PowerPoint 2007 的完全可编辑文件格式,XPS 是为 Microsoft Windows Vista 操作系统推出的标页码的固定文档格式。)
  • 直观和易于浏览的Package 文件树状结构。
  • 可以在Visual Studio的rich XML 编辑器里直接打开文件所包含的XML部分。
  • 易于使用的用户接口去添加和移除文件的相关部分部件和关连。
  • 对文件导入导出所包含的各个部分的内容。
  • 探测在Visual Studio中打开的文件是否在外部被更改了。并提示用户重载文件在不需要关闭任何打开的XML 部件编辑器。
  • 通过Visual Studio 的新建对话框提供的一套模板来创建新的Office Packages

      图一:显示了Visual Studio中打开的Open XML Package文件树形结构:

      图二:如果您双击任何文件中的XML组成部分,被点击的部分将在标准的Visual Studio XML编辑其中打开。
 
<文章结束>
      我注意到了一件事情,如果XML编辑器打开着,并且XML内容全部集中在一行上,您可以选择所有内容然后剪切并黏贴回编辑器,这时XML内容会自动进行分行并回到正常显示格式。(Ctrl + A,X,V).
还有其它9个工具包括在VSTO Power Tools 中,我强烈推荐使用如果你正在使用Visual Studio进行Office开发的话。 并且您可以查看VSTO Team BlogVSTO Developer Portal 去了解更多Visual Studio 的Office开发信息。
 
Enjoy!

  

posted on 2008-09-08 11:24:22 by VBCTI  评论(0) 阅读(4020)

一对多(主从)关系表的LINQ to SQL 实现

[原文作者]: Beth Massi

[原文链接]: One-To-Many (Master-Detail) Forms with LINQ to SQL

  前面的博客中演示了两种不同情况下如何运用Combobox Data Binding组件和LINQ to SQL对象。(分别可见Linq1Linq2.)今天我将继续讲解怎样创建一对多关系的winform表单(同时还将用到2combobox lookup list,我还会展示怎样正确的插入、更新和删除Hierarchical Data 以及怎样利用存储过程实现上述操作。

数据库模块

  在下面的例子里我将创建新的数据库而不用Northwind,这因为Northwind并不是很有代表性的数据库特别地涉及到关系完整性。我要创建的表必须包含非空外键同时又含有timestamp 字段作并发检查。

这儿是我们将要建立的数据关系图:

  我已经为每个数据表指定Update, InsertDelete存储过程。为确保数据库安全而对它做一点限制,取消Update, Insert, Delete语句的执行权限。我们只需要赋予SELECT EXECUTE 权限。 这是一种比较规范的做法,通过阻止除存储过程方式以外的任何更新操作达到有效防止恶意代码运行于数据库端的目的。

  在开始创建表单前,我要先添加一个叫“LINQ to SQL Classes”的模板文件到我的工程里来,它可以打开O/R designer让我们可以从Server Explorer Tab中拖拽数据库里的表到这个模块设计器上。那么我先把前面已经创建好的四个表拖进来,并把它命名为“OMS.dbml”,因为我建的数据库名为OMS。它会用来创建我们的LINQ to SQL类和数据库关联的引用。我还会把所有的存储过程拖拽到Method面板上。

  下一步我们想要将各种存储过程或方法关联到updateinsertdelete对应的行为上。选中一个类,然后你们可以从属性窗口中看到DeleteInsertUpdate这三个属性值都被设为“Use Runtime”。选中每个属性你就可以为每个行为指定过程或方法。你也可以简单地右键单击类对象,选中“Configure Behavior”(行为设置)。在这一屏中,你可为每个类选定相应的行为,然后选定对应的方法。

   在你建立好所有这些之后,保存模板,然后生成所有的LINQ to SQL 类和用来管理数据库连接和交互的类DataContext

数据源和表单数据绑定

   接下来我们需要把这些信息都加到Data Sources Window里以便我们快速设计好表单。这在以前的章节里我已经用Northwind演示过了,这次我想要创建一个主细表单分别是Orders和与之关联的OrderDetails并且我想把CustomerProduct这两个表以查询列表的方式出现。首先在Data菜单中选择Add New DataSource,然后选择Object Data Source Type,选择Order类后单击Finish。一系列操作完成后,Data Sources window会产生Order对象,同时还关联着OrderDetails。因为有查询功能我们还需要添加CustomerProduct

   当检查Data Sources window中类的属性时,你会同时发现受关联的父子对象。比如,如果你展开Order,可以看到Customer父表和OrderDetails子表在一起。这是因为被关联的父对象Customer是指向Order对象的。为了显示所有Customer信息我必须把Customer drop control设置成“None”,改变CustomerIDCombobox样式。同时我不想显示Modified字段,所以把它也设成“None”。然后设置Order drop control成“Details”样式。

  拖拽Order表到Form上,生成datagridview控件以及BindingNavigatorBindingSource。然后从Data Source Window上拖拽Customer表到CustomerID Combobox上,为它建立Customer数据源列表。在Combobox属性页中设置ValueMember=CustomerIDDisplayMember=LastName 以完成Customer列表的设置。

  然后拖拽Order列下的OrderDetailsForm上生成一个DataGridView。你会发现会把父对象也拖上来,这里有两个父对象Product Order。必须编辑字段列,移除掉这样的列以及Modified列。对于此例,我会只显示OrderDetailIDOrderID字段因为这些字段的值会在我们保存数据以后自动生成。同时还要更改ProductID列的类型为DataGridViewComBoxColumn,并填选Product(从Other Data Sources -> Project Data Sources -> Product.)作为数据源(DataSource)。该行为会在Form设计器的组件面板(tray)中生成ProductBindingSource组件。

同样我们需要指定 DisplayMember = NameValueMember = ProductID

LINQ to SQL 对象填充数据

  现在我们设计好了Form接口,并建立了数据绑定,已经准备好创建数据对象了,并用它们装载数据。这不像DataSetsForms应用,当我们应用LINQ to SQL类时,Form设计器不会为我们生成任何加载和保存数据的代码。但是我们可以编写更直接的LINQ 查询语句限制结果集。本例中我将包含所有OrdersCustomersProducts信息到我们的lookup列表中,但是请记住当你的数据库有成千上百条记录时,这种设计很不可取的,那就必须要查找框了。

  那么首先我们需要在Form加载事件响应处理中设置BindingSourceDataSources成我们的表对象数据。先装载Order表的所有orders记录。

Public Class Form1 

    Dim db As New OMSDataContext 

    Private Sub Form1_Load() Handles MyBase.Load 

        Me.OrderBindingSource.DataSource = db.Orders 

  然后要装载Customers列表,我们可以简单地利用Linq query得到所有的Customer name并以“LastNameFirstName”方式。

Me.CustomerBindingSource.DataSource = From c In db.Customers _

                                              Let LastName = c.LastName & ", " & c.FirstName _

                                              Select LastName, c.CustomerID _

                                              Order By LastName

  最后在我们选择Products列表时,有一个小窍门可以在列表的顶端设置一条“空”记录提示用户选择一条Products记录。而且可以通过检查ProductID0验证是否做了选择。

Dim emptyProduct As Product() = _

                {New Product With {.Name = "<Select a product>", .ProductID = 0}} 

        Me.ProductBindingSource.DataSource = (From Empty In emptyProduct).Union( _

                                              From Product In db.Products _

                                              Order By Product.Name) 

End Sub

  你们可能会想为什么没有设置OrderDetailBindingSourceDataSource。这是因为只有当我们访问Order实例下的OrderDetails集合时OrderDetails才会被自动从数据库加载。这个事件是在OrderBindingSource改变position时发生,OrderDetailsBinding需要跟随Order显示相对应的OrderDetail对象。如果想看到这些的T-SQL语句实现,只要在加载数据时调用db.Log = Console.Out就会在Debug Output 窗口显示命令语句。只是不要忘了在release编译时将其删除。 

保存Hierarchical Data

  接着我们需要在click事件中加入代码保存数据。显示如下:

Private Sub OrderBindingNavigatorSaveItem_Click() _
        Handles OrderBindingNavigatorSaveItem.Click 
        Me.Validate()
        Me.OrderBindingSource.EndEdit()
        Me.OrderDetailsBindingSource.EndEdit() 
        Try
            db.SubmitChanges() 
            MsgBox("Your data was saved.") 
        Catch ex As Exception
           MsgBox(ex.ToString)
        End Try 
End Sub 
  好了,现在我们可以尝试运行一下程序,开启一个form实例,更改、添加和保存Orders表,一切运行正常。主键字段的插入和约束关系的执行都没问题。但是,如果我们想删除一条OrderDetail(子表)记录,就会报错:

System.InvalidOperationException: An attempt was made to remove a relationship between a Order and a OrderDetail. However, one of the relationship's foreign keys (OrderDetail.OrderID) cannot be set to null.

  要解决这个问题我们要指明在OrderID被设为null的时候也可以删除OrderDetail记录。不过在O/R 设计面板上无法实现设置,必须打开它的XML编辑。而且一旦你更改了,设计器就不会消除更改除非你删除整个类。打开dbmlxml编辑页(只需要右击它选”Open with…”),里面都是关于OrderDetail类的描述。注意名为OrderDetailTable字段下的Association字段)

<Table Name="dbo.OrderDetail" Member="OrderDetails">
  <Type Name="OrderDetail">
    <Column Name="OrderDetailID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" 
           
IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="OrderID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="ProductID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="Quantity" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="Price" Type="System.Decimal" DbType="Money" CanBeNull="true" />
    <Column Name="Modified" Type="System.Data.Linq.Binary" DbType="rowversion NOT NULL" 
           
CanBeNull="false" IsVersion="true" />
    <Association Name="Order_OrderDetail" Member="Order" ThisKey="OrderID" 
                
Type="Order" IsForeignKey="true"/>
    <Association Name="Product_OrderDetail" Member="Product" ThisKey="ProductID"
                
Type="Product" IsForeignKey="true" />
  </Type> 

  我们需要增加一个叫做DeleteOnNull的属性,设置值为true,这样就能在调用SubmitChanges()方法上传时不受关系约束地删除一条子表的记录了。

<Association Name="Order_OrderDetail" Member="Order" ThisKey="OrderID" Type="Order"
            
IsForeignKey="true" DeleteOnNull="true"/>        

  另一个解决办法是修改数据库中关联的Delete规则,设置它为"Cascade"。这样设计器会确保将DeleteOnNull属性引用至Association中。

  好了,我们再次执行form,然后尝试删除一条OrderDetail子表的记录,并点保存,保存无误。但是如果你删除一整条order记录时,保存仍然会报错:

System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_OrderDetail_Orders". The conflict occurred in database "OMS", table "dbo.OrderDetail", column 'OrderID'.
The statement has been terminated.

  这是因为和DataSets不同,当删除一条父表记录时,O/R Designer不会级联删除子表的记录。所以我们需要手写代码实现这种方式。可以有很多不同的方法达到,而且有一种方法是我以前的文章里演示过了的。

    Private Sub BindingNavigatorDeleteItem_Click() _
        Handles BindingNavigatorDeleteItem.Click 
        If Me.OrderBindingSource.Position > -1 Then
            'Grab a reference to the currently selected order
            Dim order As Order = CType(Me.OrderBindingSource.Current, Order)            
            'Ensure that children are deleted when the parent is deleted
            For Each detail In order.OrderDetails
                db.OrderDetails.DeleteOnSubmit(detail)
            Next
        End If
    End Sub 

  现在可以运行form,并尝试UpdateInsertDelete各种数据操作了,一切都会顺利的完成。如果你开启了DataContextLogging功能或者利用数据库的SQL profiler工具,你就可以监控存储过程的调用情况。

 

    下次我会教你如何通过创建business基类来继承,而且使用IdataErrorInfo接口和ErrorProviderLINQ to SQL类添加简单的验证。

    更新:我把代码放在a Code Gallery project,你可以试试。

Enjoy!

posted on 2008-03-21 14:24:08 by VBCTI  评论(0) 阅读(6002)

Visual Studio 2008中的新元件TableAdapterManager

[原文作者]: Beth Massi

[原文链接]: The New TableAdapterManager in Visual Studio 2008

        在我以前的TableAdapters and Transactions文章中,我展示了一套关于如何在多层数据表结构的数据集中完成事务内部的分等级更新。我在这里给出的例子演示了Visual Studio 2005如何用数据库事务或者TransactionScope去更新数据。这需要一些代码来管理行的正确更新顺序,TableAdapters上的连接和处理,以及在回滚的情况下,你希望保持数据集中原有的变化。

        幸运的是,在Visual Studio 2008DataSet生成器有了新的提高,它创建了一个新的类通过只有几行代码自动处理所有这些事情!进入TableAdapterManager

        在这篇文章中,我将要借用以前的应用程序同时把它升级到Visual Studio 2008,要清楚的一点是,我将不会去升级它的目标框架,因为我希望程序能继续在NET Framework 2.0上运行。(Visual Studio 2008 可以通过一个叫做Multi-Targeting的特性去开发支持NET 2.0, 3.0 and 3.5 Frameworks的应用程序。关于这方面的更多使用方法,请看这里

    当你在 Visual Studio 2008中打开NorthwindTransaction.sln文件,它将会启动一个升级项目向导,使sln项目文件兼容于Visual Studio 2008. 启动以后,在资源管理器中双击OrdersDataSet 以打开数DataSet Designer。在属性视窗中会看到“分等级的更新” 选择True并保存. 这表示告诉数据集发生器去生成了TableAdapterManager . 注意每当你创建新的数据集时,默认的就是True

 

    现在,展开OrderForm以打开Windows Form设计器,删除工具箱顶部的Dropdown控件(TransactionToolStripDropDownButton)因为我们现在只要有一个简单的Save() 方法。右键点击窗体察看代码,在OrdersBindingNavigatorSaveItemclick事件中移掉Case语句,用Me.Save()代替:

Private Sub OrdersBindingNavigatorSaveItem_Click() Handles OrdersBindingNavigatorSaveItem.Click

    Me.Validate()

    'Commit all data to the OrdersDataSet

    Me.OrdersBindingSource.EndEdit()

    Me.Order_DetailsBindingSource.EndEdit()

    If Not Me.OrdersDataSet.HasErrors Then

        If Me.OrdersDataSet.HasChanges Then 

            If Me.Save() Then

                MessageBox.Show("Your changed have been saved.", Me.Text, _

                                MessageBoxButtons.OK, MessageBoxIcon.Information)

            Else

                MessageBox.Show("Your changes could not be saved!", Me.Text, _

                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

            End If

        Else

            MessageBox.Show("Please make changes first.", Me.Text, _

                            MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

        End If

    Else

        MessageBox.Show("Please correct the errors with this data first.", Me.Text, _

                        MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

    End If

End Sub

    现在到了很有趣的部分。我们将删除所有的Savexxx()方法并且改写成使用新的TableAdapterManager简单的Save()方法。这个代码和以前用的SaveInDatabaseTransaction() 方法的效果是一样的。

''' <summary>
''' Performs an ordered save so that keys are properly updated in
''' the child table and so that deleted child rows are submitted
''' to the database first inside a database transaction automatically. 
''' VS 2008 generates code that takes care of the saving in the proper
''' order in a database transaction.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function Save() As Boolean
    Dim saved As Boolean = False 
    If Me.OrdersDataSet.HasChanges Then
        Try
            Dim manager As New OrdersDataSetTableAdapters.TableAdapterManager 
            'Back up the dataset so that if the transaction fails, then the entire 
            ' dataset is restored to it's original state.
            manager.BackupDataSetBeforeUpdate = True 
            manager.Order_DetailsTableAdapter = Me.Order_DetailsTableAdapter
            manager.OrdersTableAdapter = Me.OrdersTableAdapter 
            saved = (manager.UpdateAll(Me.OrdersDataSet) > 0) 
        Catch ex As Exception
            MsgBox(ex.ToString)
        End Try
    End If 
    Return saved
End Function

    发现没有,现在的代码简洁多了。以前我们必须去管理Order的更新,连接和事务处理,以及回卷的状态。现在所有这些都照顾到了。既然我们不再需要用到它,我们也可以在form中移掉MergeAfterSave方法以及TableAdapter partial 类中的AssignConnection方法。注意,如果我们已经创建了一个新的窗体,那么它就已经为我们创建了TableAdapterManager实例,预置了TableAdapter实例。

    运行应用程序选择一个客户的订单,然后在窗体上添加、更新和删除父子行。当我们点击保存按钮,这些行将会被更新和插入到parent-child order中,而在child-parent order删除,所有的这些都在数据库事务内部完成。一旦你打开SQL Server Profiler,我们可以看到:

       你也可以通过设置UpdateOrder属性来指示TableAdapterManager你想先处理更新过程。更多信息请查看TableAdapterManager文档

       TableAdapterManager为我们省去了很多代码,但是你仍需要确认你已经完全地设置好你的数据集去处理foreign-key约束,就像我在这片文章中提到的,TableAdapters按特定的顺序调用Fill方法。更多详细信息请阅读这篇文章,关于在你的数据集中使用foreign-key约束,这样能够通过TableAdapterManager完全更新。我已经附上更新好的程序.

Enjoy!

Attachment(s):NorthwindTransaction2008.zip

posted on 2008-03-07 16:59:08 by VBCTI  评论(4) 阅读(4765)

Northwind遇到虚拟地球——利用Linq生成VE(虚拟地球)地图

[原文作者]: Beth Massi

[原文链接]: Northwind Meets Virtual Earth - Generate VE Maps with LINQ

利用VB9Linq,你可以很容易地从多个数据源之间创建XML文件,这些数据源包括关系型数据、 XML文件和其它任何可查询对象,因为绝大多数流行的系统之间的交互都是以XML的格式进行的,所以应用是很广泛的。在VB9SOAPXAMLHTMLRSS都能够很容易地通过Linq To XML创建。比如, 如果我们想在一个VE生成的地图中显示所有Northwind库中的Customers信息,我们需要怎么做?

VE允许我们传递给它一个RSS文档,文档里面的每个item指定了纬度和经度以便能够绘制出他们在地球上的每个位置;这里有两种不同的格式可以传递,其中之一就是GeoRSS标准,我们需要做的就是通过获取customers表中包含的地址信息中的经纬度来创建这个XML,并且将这个GeoRSS传递给虚拟地球;我们可以利用http://geocoder.us 中的服务来获取在美国的Customers经纬度信息,这个服务能够从许多格式的美国地址(包括Restful RDF)中返回一组对应的经纬度信息,为了从Northwind中的customers表中创建GeoRss,我们可以在Linq查询中使用这个服务。

假设我们在Server Explorer中已经有一个到Northwind库的连接(或者其它的含有地址的数据库也可以),首先添加一个“LINQ TO SQL classes”到你的项目中,将它命名为Northwind.dbml ,并且将Customers表从Server Explorer中拖拉到设计器中,下一步就是在代码文件的头部引入Geo命名空间,因为geo命名空间的http://geocoder.us 服务将返回XML文件,我们将使用到XML文件里的地址信息。 

Imports <xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">

现在我们可以写一个查询来为customers创建GeoRSS,因为Northwind库中包含的地址绝大多数是编造的,你可以将这些地址改成真实的,或者我们可以选择居住在Oregon (OR)Customers,因为那有一些有效的地址。

Dim db As New NorthwindDataContext

Dim geoRSS = _

<rss xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">

    <channel>

        <title>Northwind Customer Locations</title>

        <link></link>

        <%= From Customer In db.Customers _

            Let Desc = Customer.Address & ", " & Customer.City _

            Let Address = Customer.Address & "," & Customer.PostalCode _

            Where Customer.Country = "USA" AndAlso Customer.Region = "OR" _

            Select <item>

                       <title><%= Customer.ContactName %></title>

                       <description><%= Desc %></description>

                       <%= GetGeoCode(Address).Descendants %>

                   </item> %>

    </channel>

</rss>

在这个查询中我们建立了GeoRSS并且调用了一个使用者,里面定义了函数GetGeoCode,该函数接受customer的地址参数,返回它的经纬度;注意,我们在查询中使用了Let关键字,目的是为descriptionaddress创建查询变量,这些变量在下面建立<item>时将会使用到。如果有结果,函数GetGeoCode将返回一个包含位置信息的XElementXElement的子方法在查询中被回调,为的是在GeoRSS中安放<geo:lat> <geo:long>节点。 

Function GetGeoCode(ByVal address As String) As XElement

    Dim url = "http://geocoder.us/service/rest/?address=" & Server.UrlEncode(address)

    Try

        Dim geo = XElement.Load(url) 

        Return <location>

                   <%= geo.<geo:Point>.<geo:long> %>

                   <%= geo.<geo:Point>.<geo:lat> %>

               </location> 

    Catch ex As Exception

        Return <location></location>

    End Try 

End Function

现在我们已经拥有了GeoRSS,可以将它传递给虚拟地球,以利用它来创建地图。例如,我们可以仅仅创建一个简单的ASP.net应用,并且将创建好的GeoRSS保存到一个Session变量中。默认页面需要有JavaScript代码,我们将利用它把GeoRSS传递到虚拟地球,并且发送一个ID”mymap”<div>块,块中定义了在页面上安放地图的区域。(想了解更多的API信息,可以看一下虚拟地球文档 

<%@ Page Language="vb" AutoEventWireup="false"

CodeBehind="Default.aspx.vb" Inherits="NorthwindVirtualEarth._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Northwind Customers on Virtual Earth</title>

    <link href="style.css" rel="stylesheet" type="text/css" />

    <script type="text/javascript"

        src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5">

    </script>

    <script type="text/javascript">

        var map = null;

        var layerid=1;

        function GetMap()

        {

            map = new VEMap('myMap');

            map.LoadMap();  

            var l = new VEShapeLayer();

            var veLayerSpec = new VEShapeSourceSpecification(VEDataType.GeoRSS, "georss.aspx", l);

            map.ImportShapeLayerData(veLayerSpec, null);

        }

     </script>

</head>

<body id="body" runat="server" >

   <form id="form1" runat="server">

   <h1>Northwind Customers on Virtual Earth</h1>

   <div id='myMap' style="position: relative; width: 800px; height: 400px;">

         <asp:Label ID="lblStatus" runat="server" Text="No items found" Visible="False"></asp:Label>

   </div>

   </form>

</body>

</html>

Default.aspxVB后台代码只是简单地做了检查,看看是否有任何从geoRSS查询中返回的<item>元素,如果有,则动态地在bodyonload事件里添加调用GetMapjavascript函数代码。 

If geoRSS...<item>.Count > 0 Then

    Session("georss") = geoRSS 

    Me.body.Attributes.Add("onload", String.Format("GetMap()"))

Else

    Me.lblStatus.Visible = True

    Session("georss") = <rss></rss>

End If

另外一个页面是GeoRss.aspx,是一个空白页,仅仅返回存储在session变量中的GeoRSS,正是javascript方法需要调用的内容。 

Public Partial Class GeoRSS

    Inherits System.Web.UI.Page 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 

        Dim georss As XElement = CType(Session("georss"), XElement) 

        Response.ContentType = "text/xml"

        Response.Write(georss.ToString()) 

    End Sub

End Class

这里关键的地方在于一个Linq查询语句中跨越了多个数据源(Northwind库和geocoder.us服务),而且只创建了一个单一的XML文档,它符合GeoRSS标准,将它传递到虚拟地球,用来产生我们的地图。正如你在这个RSS示例中所看到的,利用LinqVB9从多数据源中创建XML是非常简单地一件事。代码附件

如果你对利用WPF和你的数据创建动态地图感兴趣,不要忘记参阅WisniewskiDevCenter featured 中的文章 Create Dynamic Maps with Visual Basic 9.0 and WPF.

posted on 2008-03-06 11:22:59 by VBCTI  评论(0) 阅读(4114)

最新Power Packs发布的Data Repeater控件

[原文作者]: Beth Massi

[原文链接]: New Data Repeater Control in Latest Power Packs Release! (Beth Massi)

 

试一试刚刚发布的Visual Basic Power Packs 最新版本,包括了Data Repeater控件。Power Pack是一个免费的插件,控件,组件以及工具的集合,这使得开发.NET程序变得更加容易。

来自Power Packs团队:

我们宣布Microsoft Visual Basic Power Packs!第三版今天发布了。

该版本包括了Data Repeater控件,你可以使用标准的Windows Forms控件将数据按行显示在可滚动的容器里,这样你就可以用更加复杂的,定制的控件而不是标准的grid控件。

如何使用Data Repeater控件创建一个自定义的像Windows Form的控件来显示数据,看下面这个快速实例。这个例子使用了2Data Repeater控件,一个用默认的垂直布局显示当天的天气预报数据,另一个使用了水平布局显示了5天的天气预报数据。

Visual Basic Power Packs最新版里还包括了之前Visual Basic Power Packs的更新,Line Shape控件,PrintForm组件以及Printer Compatibility Library

你可以点击这里下载最新的Visual Basic Power Packs

谢谢

The Visual Basic Power Packs Team

你也可以点击Channel 9 screen-cast ,我和QA teamJohn Hart from录制了如何使用这些控件。

Enjoy,
-Beth Massi, Visual Studio Community

posted on 2008-02-21 15:48:00 by VBCTI  评论(1) 阅读(4782)

最新文章:Windows Workflow 101

[原文作者]: Beth Massi

[原文链接]: New Community Article - Windows Workflow 101 (Beth Massi)

 

昨天我们在Visual Basic Developer Center发表了一篇Maurice DeBeijerVB MVP写的Windows Workflow入门,名为Windows Workflow 101这是Maurice写的Workflow系列中的第一篇。如果你正在为你的应用程序中如何使用这个技术绞尽脑汁,那么这篇文章是非常好的开始。之后的大量文章将会探究高级场景以及用户行为。多谢Maurice

Enjoy

-Beth Massi, Visual Studio Community

posted on 2008-02-20 12:06:00 by VBCTI  评论(1) 阅读(3636)

Interop Forms Toolkit结合MySettings和WCF Configuration的应用

[原文作者]Beth Massi

[原文链接]Using My.Settings and WCF Configuration with the Interop Forms Toolkit

       最近有客户问到如何将基于Interop Forms Toolkit开发的Interop User Control通过读取app.config项目文件调用WCF service。(如果你不熟悉Interop user controls 开发请见can read these posts  watch these videos。)由于Interop user controls被编译成DLL文件,而生成的设置文件也以它命名,并不紧随VB 6应用程序,所以在运行时Interop User Control无法读取该文件里的设置。

      解决这个问题有一个简单的窍门,只要把bin目录下的.dll.config文件重命名成 VB6 EXE的名称,然后替换掉VB6 EXE所在目录下的同名文件。这样,运行WCF service client才能够读取指定的设置。

      或者你可以利用Build Events自动地为你去做这件事。只要双击Solution ExplorerMy Project打开项目属性,选择Compile栏,点击”Build Events”按钮,然后点”Edit Post-build…”. 在弹出的窗口中输入并执行复制命令,如:

copy /Y "$(TargetDir)$(ProjectName).dll.config" "$(ProjectDir)..\..\VB6App\Project1.exe.config"

      现在每当你更新Interop user control项目下的app.config文件,生成的.config settings就会被重命名和拷贝到VB6 应用程序目录下。

      然后我们谈谈My.Settings的应用, My.Settings使.NET程序存取应用级和用户级设置更简单,他们也是存放在app.config里的。但是仅仅重命名是不起作用的,因为My.Settings用到一种叫Settings Provider的驱动程序。在Visual Studio2005中默认的驱动者运用configuration系统加载和保存设置,生成设置文件,但是与WCFsettings的读取方式不同。

除了要重命名config文件,我们还要修改section标题下的name值成VB6EXE的名称(见添加的粗体字部分):

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

    <configSections>

        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >

            <section name="AMyInteropUserControlLibrary1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />

          <!--

          Using userSettings in a VB6 application:

          Change the name here to the name of your VB6 .EXE -->

            <section name="Project1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />

        </sectionGroup>

        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >

            <section name="AMyInteropUserControlLibrary1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />

          <!--

          Using applicationSettings in a VB6 application:

          Change the name here to the name of your VB6 .EXE -->

          <section name="Project1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />

        </sectionGroup>

    </configSections>

如果你通过项目属性的Settings栏新增设置, section groups就不会消失。现在你可以在项目部署以后更改.exe.config文件让你的Interop user control能够动态的读取设置。我已经附上完成的例子,包含WCF service, 为你在Visual Studio2005下演示。请阅读Readme文件以保证正确安装。

SettingsAndInteropUserControl.zip

posted on 2008-01-08 14:00:00 by VBCTI  评论(0) 阅读(4309)

Visual Studio - 创建Add-In 工程

[原文作者]: Beth Massi

[原文链接]:  Visual Studio Tip of the Day - Let's Build an Add-In

我已经开始使用Visual Studio 创建一些Add-In的工程, 这的确是一个比较容易的过程. 真正值得高兴的是,我们不需要去单独安装SDK 去创建Add-in工程,这已经集成在Visual Studio. 我之前写过一个从Sararss feed浏览他的文章的程序.现在我们看看怎么用Visual Studio 2008实现的. 你可以从这里下载代码.

首先我们添加一个新的工程到你的项目中, Add --> New Project, 展开 "Other Project Types"选择”Extensibility” 然后选择”Visual Studio Add-In”.

这会打开一个向导帮助你配置一些你的add-in程序.第一步是选择语言”Create an Add-in using Visual Basic”, 进行到下一步.

下一步选择add-in程序的宿主.在这个程序中我们选择 Visual Studio 2008 Macros”,因为我只想让这个程序起运并显示在工具菜单栏上.

再下一步是Add-in程序的名字和描述. 我们把名字和描述改成 Visual Studio Tip of the Day".

下一步,向导会问我们怎么样去起运Add-in程序, 在这个程序中我们需要在Visual Studio打开的时候载入,并且提供一个在工具栏上的菜单.选择相应的选项,进行到下一步.

最后一步,向导会我们是否需要一个”About”,我们会跳过这一步, 结束向导.

一个新Add-in工程会被加入到你的项目中.  然后打开Add-in工程的属性页,选择”Compile”, 进入”Advanced Compile Options…”,Target Framework改成 .NET Framework 3.5

我们需要添加引用到我们上文提到的(SaraRssViewer) 工程, 现在我们已经有了引用并且配置好了target framework,打开Connect.vb 文件,” OnStartUpComplete”方法中我们会加入代码去载入窗体.

Public Sub OnStartupComplete(ByRef custom As Array) _

  Implements IDTExtensibility2.OnStartupComplete

 

    Dim frm As New SaraRSSViewer.Form1

    frm.Show()

End Sub

当你从工具菜单选择我们的Add-in的时候, 这个程序会被运行, 我们需要把同样的代码加入Exec方法.

Public Sub Exec(ByVal commandName As String, _

                ByVal executeOption As vsCommandExecOption, _

                ByRef varIn As Object, ByRef varOut As Object, _

                ByRef handled As Boolean) _

           Implements IDTCommandTarget.Exec

 

    handled = False

    If executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault Then

        If commandName = "MyAddin1.Connect.MyAddin1" Then

 

            Dim frm As New SaraRSSViewer.Form1

            frm.Show()

 

            handled = True

            Exit Sub

        End If

    End If

End Sub

 

然后我们按F5运行我们的程序, 这样你就可以看到我们的Add-in程序运行的效果,

你也可以自己定制你的菜单名,OnConnection方法中,定义Command变量,第三个参数是显示菜单名,第四个是改变tolltip.

Dim command As Command = _

    commands.AddNamedCommand2(_addInInstance, _

                              "MyAddin1", _

                              "Tip of the Day", _

                              "Display the Visual Studio tips of the day", _

                              True, 59, Nothing, _

                              CType(vsCommandStatus.vsCommandStatusSupported, Integer) + _

                              CType(vsCommandStatus.vsCommandStatusEnabled, Integer), _

                              vsCommandStyle.vsCommandStylePictAndText, _

                              vsCommandControlType.vsCommandControlTypeButton)

当我们编译Add-in工程的时候,会把.addin 文件添加到Visual Studio 2008\Addins\文件夹下面. 通过这个例子,我们可以看创建一个Add-in程序的确很简单.你也可以一些尝试其他的例子,比如OfficeAdd-in等等.

在以后的文章中,我会写一些关于如何把Add-in程序部属到其他机器的方法.

 

posted on 2007-12-25 16:06:00 by VBCTI  评论(8) 阅读(5098)

LINQ to SQL and One-To-Many Relationships

[原文作者]Beth Massi

[原文链接]LINQ to SQL and One-To-Many Relationships

 

最近,有人问我关于发表在creating a one-to-many form with LINQ to SQL 上的一个视频的一个问题。在这个视频示例中,我用Northwind数据库,在CusomersOrders两个实体间建立了一对多的关联。我们总是喜欢用Northwind做示例,但是有时候我自己都忘记了它的设计有多么不切实际,例如随处可见的nullable foreign keyschild table的外健值(例如Orders表中的CusomerID或者Product 表中的CategoryID)被设置为nullable也就表示这些child rowsparent table中可以没有对应的row存在。

通常情况下,对于主外健关联,外健值是设置为non-nullable,就是为了child rows parent table中的row相关联的,这也是为了保证数据库中数据的完整性。外健值为nullable这种情况一般用于查询,这时查询的关键字可以为空。外健值设置为non-nullable,也表示在删除child tablechild rows前,首先要先删除parent table中所对应的row(除非你明确设置主外健关联的delete rule "Cascade")。下图是CustomersOrders的典型实例:两个表建立主外健关联,外健值设置为non-nullable

 

当我们应用O/R DesignerLINQ to SQL时,对于主外健关联,我们就不得不考虑删除子表数据的异常情况(当主外健关联的delete rule设置为默认值"No Action"并且外健值不允许为空)。所以,当你在界面上删除外健值不允许为空的子表的数据,然后调用DataContext类中的SubmitChanges()方法来提交删除结果时,会弹出错误提示框。例如,当我们绑定LINQ to SQL classes 到两个grid 并且只是删除子表Order 的记录时,然后调用SubmitChanges()方法,系统会报错:“An attempt was made to remove a relationship between a Customer and a Order. However, one of the relationship's foreign keys (Order.CustomerID) cannot be set to null."”(这个错误信息是由DataContext调用的ChangeTracker发出的,这样删除结果就不会被提到数据库。)

 

其实在我们利用O\R designer通过拖拽数据表到设计界面来建立实体时这样的错误并不明显,那么我们可以更深一层,通过实体类来了解一下实体间的关联。当我们把表Customers和表OrdersServer Explorer拖拽到O\R designer界面后,实体CustomerOrder及它们之间的关联就会自动生成:

参考一下dbmlxml文件(Solution Explorer 窗口,右击dbml 文件,选择Open With --> XML Editor) 请注意Order 实体下的association element

<?xml version="1.0" encoding="utf-8"?>

<Database Name="MyData" Class="MyDataDataContext" xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">

  

<Connection Mode="AppSettings" ConnectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\MyData.mdf;Integrated Security=True;User Instance=True" SettingsObjectName="LINQtoSQLDeleteOnNull.My.MySettings" SettingsPropertyName="MyDataConnectionString" Provider="System.Data.SqlClient" />

 

  <Table Name="dbo.Orders" Member="Orders">

    <Type Name="Order">

      <Column Name="OrderID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

      <Column Name="CustomerID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />

      <Column Name="OrderDate" Type="System.DateTime" DbType="DateTime" CanBeNull="true" />

      <Association Name="Customer_Order" Member="Customer" ThisKey="CustomerID" Type="Customer" IsForeignKey="true" />

    </Type>

  </Table>

  <Table Name="dbo.Customers" Member="Customers">

    <Type Name="Customer">

      <Column Name="CustomerID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

      <Column Name="Name" Type="System.String" DbType="VarChar(50)" CanBeNull="true" />

      <Association Name="Customer_Order" Member="Orders" OtherKey="CustomerID" Type="Order" />

    </Type>

  </Table>

</Database>

如果要在调用SubmitChanges()时,能够单独删除子表数据,需要在association的定义中,添加属性DeleteOnNull="true"(目前这个属性只能在dbml文件里手动添加,还不能在界面上设置,但幸运的是,只要我们不将界面上的实体全部删除,在dbml文件里对属性的修改都会保存下来,不会因为页面的切换而被刷掉),这样,就可以单独删除子表数据并成功地提交到数据库中。

<Association Name="Customer_Order" Member="Customer" ThisKey="CustomerID" Type="Customer" IsForeignKey="true" DeleteOnNull="true"/>

还有一种方法,就是在数据库中把主外健关联的Delete Rule设置为"Cascade",这样O\R designer会根据这个属性的值来处理。如果你希望任何删除customer数据的操作,都要自动地删除相关联的orders数据,那么采用这种方法会更好些。这样,你应用了Cascade delete rule在你的database relationship,这样你就不必每次想要利用Datacontext删除Parent Table数据的时候先手动把Children Table里面的相关数据先删除了,(注意:把主外健关联的Delete Rule设置为"Cascade",只需要在Server Explorer窗口中右击parent表, 选择"Open Table Definition", 右击任何一列,点击 "Relationships", 选择相应的关联,扩展 "INSERT and UPDATE Specification" ,然后设置Delete Rule CASCADE.)

但是,有时候我们不能够更改数据库,那么我们可以在DataContext partial classDeleteCustomer partial method 里添加一些代码(参考下面的实例),这样在customer数据被删除前,相应的orders数据会被自动删除(注意:在RTM版本,可以通过右击O\R designer界面,选择"View Code"来获得partial class代码,但在Beta 2版本,我们不得不手动创建partial class)。

 

Partial Class MyDataDataContext

 

    Private Sub DeleteCustomer(ByVal instance As Customer)

        For Each o In instance.Orders

            'Always delete the orders before the customer is deleted.

            'This means that the database relationship's delete rule

            ' does not need to be modified to CASCADE.

            Me.ExecuteDynamicDelete(o)

        Next

        Me.ExecuteDynamicDelete(instance)

    End Sub

End Class

可以参考一下示例attached the example,它是应用了VS 2008 Beta 2版本。总之LINQ to SQL采用了很简洁的方式进行数据访问,尤其是它可以在后台自动处理更新和事务。而我们只需要关注从你的数据库schema中映射出来的associations的类型就可以了,而designer 如何实现这些步骤我们就不用关心了.

 

 

posted on 2007-12-21 11:20:00 by VBCTI  评论(4) 阅读(3841)

VB动态编程实例

[原文作者]Beth Massi

[原文链接]An Example of Dynamic Programming in VB

在论坛里已经有很多人问我要VB动态编程的实例。大家都已经了解使用动态编程的优势,但对于如何在VB中使用的一些细节仍不太清楚。由于动态编程这个词是在最近才开始流行的,现在仍有很多读者不熟悉这个术语,对于这些读者,我想首先要简单介绍一下什么是动态

其实我的背景也是来自于一种类似Visual Basic动态编程语言,但是相比于VB,他有更好的数据处理和综合查询能力,并且从1995年后,他也更倾向于面向对象编程。这个语言就是Visual FoxProVFPVB都具有的一些优势包括他们都很容易对对象进行操作,并且在设计时并不要求完全明确数据的类型,也就是所谓的弱类型的语言。VFP所特有的优势是能够动态执行代码,较强的交互性和支持游标,对矩阵数据源的综合查询能力。也丰富了基于元数据的编程风格,使它能够在运行过程中改变应用程序的行为而不需要重编译。VFP 的缺点是它是完全的动态,而没有任何的静态输入检查。大家都知道你要是不仔细的话,它会给你带来很大的麻烦。。

现在VB 9既有集成查询(Linq,并且它比VFP更完善)又有静态类型检查,所以我会选择VBVB支持动态类型,当需要的时候,也支持动态类型。这是我所知的唯一一种兼具静态和动态类型能力的语言。对于那些我曾经专注于开发的基于数据和信息系统的应用程序来说,这是一个巨大的胜利。我们需要能够简单的对这类应用程序进行设置和个性化而不需要重编译,同样我们也需要一种语言能够让我们在简单的做这些操作的同时也能够减少写代码的工作。

Visual Basic动态编程的构建和交互性仍有很大的拓展空间,并且我认为在VB 10里我们就能够看到这些改进,不过,我还是想向你展示一下使用VB 8 (VS 2005)VB 9 (VS 2008)来做动态编程的优势。为了证实这点,我会创建一个简单的动态UI界面,他会通过读取一个XML文件来在Windows Form界面上显示一些控件。我们将用VB 8来做这个工作,同时我会在使用VB 9VS 2008开发程序的时候,演示一些XLinq的使用

一个VB 8.0, VS 2005动态编程实例

我已经创建了一个名为questions.xmlXML文件,该文件包含了一些关于一个调查表格的信息,我想通过这些信息动态的创建这个调查表。它不仅包含了这些问题的本身,同时也包含了他需要显示的控件类型(和一个能够找个这个控件的集合),还有一些其它的附加属性如前景色和背景色:

<?xml version="1.0" encoding="utf-8" ?>

<questions>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the first survey question.</text>

    <height>35</height>

    <forecolor>Blue</forecolor>

    <backcolor>Control</backcolor>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the second survey question.</text>

    <height>100</height>

    <forecolor>Red</forecolor>

    <backcolor>Pink</backcolor>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.Label</control>

    <text>This is the third survey question.</text>

    <height>80</height>

    <forecolor>Cornsilk</forecolor>

    <backcolor>Black</backcolor>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.Button</control>

    <text>This is the fourth survey question.</text>

    <height>30</height>

    <forecolor>HotPink</forecolor>

    <backcolor>Cyan</backcolor>

  </question>

</questions>

然后我就有了一个名为Form1Windows Form。在这个form的滚动区域,我想显示一些固定的内容和一些动态的内容。为了实现这个,我添加了一个Panel,并且让他可以滚动,然后在panel的内部加了一个TableLayoutControl。我把这个控件设置为固定有2(column),即提问和回答,但是他会动态的增加行数(rows) ,行数是基于XML 文件中问题的数量。为了能够兼容早于VB 8XML版本,我会把他载入一个DataSet,这样我们就能循环创建行并且动态的创建控件:

Dim myData As New DataSet()

myData.ReadXmlSchema(CurDir() & "\questions.xsd")

myData.ReadXml(CurDir() & "\questions.xml")

Dim survey As DataTable = myData.Tables(0)

 

'Now add all the questions defined in the questions.xml file

For Each row As DataRow In survey.Rows

    Me.AddQuestion(row)

Next

但是为了增加程序的复杂性,我还想包含一个由我自己创建的名为QuestionInfo的静态问题对象。这样我们需要显示的对象就包含了一组静态定义的提问和一组动态信息,这是来自于XML 文件的提问。我的QuestionInfo对象是一个简单的类,他就定义了一个默认的属性(很快你就会看到为什么他很重要):

回到我们的form,我想创建一个QuestionInfo对象,并且把它作为一个提问添加到我们的form上:

Me.AddQuestion(New QuestionInfo())

现在让我们做一些动态编程。我们来创建一个动态类,他会读取传入的信息对象(在这个程序里就是DataRow对象或者QuestionInfo对象),然后会返回那个对象所描述的控件。让我们用AddQuestion方法来调用着个动态类:

Private Sub AddQuestion(ByVal info As Object)

    ''Dynamic' is a class we created that dynamically creates

    ' the control and sets properties. Notice that info parameter

    ' is an object. As long as it has the fields defined that we need,

    ' this will work as expected. Take a look at Dynamic.vb file

    ' to see how VB helps with dynamic programming.

     Dim tbQuestion As Object = Dynamic.GetQuestion(info)

 

    If TypeOf tbQuestion Is Control Then

        With tbQuestion

            .Dock = DockStyle.Fill

            .TabStop = False

        End With

        Me.TableLayoutPanel1.Controls.Add(tbQuestion)

        Me.AddAnswer() 'Each question has an answer

    Else

        'We could do something else with this object.

    End If

End Sub

 

现在,在涉及动态类的代码以前,我想要提醒一下,程序工程是设置为Option Strict ONOption Explicit ON的,这样这个编译器就会为我们做静态的类型检查。注意我们要把动态类的代码写在一个完全独立的文件中,而且需要将这个文件设置为Option Strict Off 。保证你只是把含有动态类的代码文件设置了Option Strict Off因为在设计时,编译器的类型检查能帮助我们减少代码中的Bug

Option Strict Off

 

Public Class Dynamic

 

    ''' <summary>

    ''' Dynamically creates an object and sets properties on it

    ''' by reading properties on the passed in object.

    ''' </summary>

    ''' <param name="info">An object with the following properties:

    ''' assembly

    ''' control

    ''' height

    ''' text

    ''' forecolor

    ''' backcolor</param>

    ''' <returns></returns>

    ''' <remarks>If these properties are not present a runtime error occurs.

    ''' In that case a multi-line Textbox is returned with the error message.</remarks>

    Shared Function GetQuestion(ByVal info As Object) As Object

        Dim c As Object

        Try

            c = System.Reflection.Assembly.Load(info!Assembly).CreateInstance(info!Control)

 

            'VB does an automatic conversion at *runtime* when

            ' setting these properties.

 

            'This is because Option Strict is set to Off.

 

            'Option Strict can be set on a file-by-file basis only

            ' so make sure you separate your dynamic classes and

            ' methods (via partial classes) into separate files.

 

            c.Height = info!Height

            c.Text = info!Text

            c.ForeColor = Color.FromName(info!ForeColor)

            c.BackColor = Color.FromName(info!BackColor)

            'c.Text = info!Cool 'This will cause a runtime error

 

            If TypeOf c Is TextBox Then

                c.ReadOnly = True

                c.MultiLine = True

                c.ScrollBars = ScrollBars.Vertical

            End If

 

        Catch ex As Exception

            'Try/Catch is required here, as this code will cause

            ' a runtime error if the info object is missing fields

            ' or the type cannot be created.

            c = New TextBox

            c.Text = ex.ToString

            c.MultiLine = True

            c.Height = 100

            c.ReadOnly = True

            c.ScrollBars = ScrollBars.Vertical

        End Try

 

        Return c

    End Function

End Class

 

这里,通过!操作符来读取我们想要的属性,vb会自动将这些属性的值返回给我们。 同时也请注意我们是如何构建异常处理的:将异常信息写入一个文本框,并且返回这个文本框。在动态编程的时候,异常处理是非常重要的,因为相比于静态编程,动态编程写出的程序在运行的时候更有可能出错。

运行起这个程序以后,在form的可滚动区域,我们既可以看到我们的静态的question,也能够看到来自于XML 文件的所有动态的question

 

以上就是我们在VB 8Visual Studio 2005中的操作。

同样的例子,使用VB 9.0VS 2008,和指向XMLLINQ连接 

现在我们想把这个程序升级到VB 9VS 2008。为什么要升级?为了展现XMLLINQ连接的优势,和它所带来的智能感知。这样我们就可以提升我们程序的安全性。在你编码的时候,很重要的一点就是,能够注意到动态编码和静态编码的平衡性。

当我们用VB 9来创建一个工程时,默认的情况下,Option Strict是设置为Off的。不过,这里有一个新的设置,名为Option Infer,并且它的设置是On。这就意味着我们不需要很明确的指定本地变量的类型,因为编译器会对变量进行类型推断。当编译器对变量进行类型推断的时候,它们实际是强类型的。类型推断能够更好的提供对动态编程的支持。

首先我们需要做的是引用一些命名空间,包括:System.LinqSystem.Xml.LinqXML的命名空间。接着,通过在VS中打开XML文件并且在XML文件的菜单栏中选择创建Schema"来为 questions.xml 文件创建了一个XSD schema。然后我把这个schema保存在工程中。这样,我们就能够引用这个命名空间了:

Imports <xmlns:ns="http://www.w3.org/2001/XMLSchema">

引用了上面namespace后,在我们处理包括questionXElement的时候,在VS IDE里就有了智能感知。在动态编程过程中,智能感知能给我们很大的帮助。这样,我们就不需要将XML导入到DataSet中,而是可以创建一个基于XMLLINQ查询(也就是XLinq),这个查询可以返回给我们一组Xelement对象的集合。然后我们来修改动态类使其能够处理这些元素而不是通常的对象类型。首先读入XML 文件:

Dim survey = XElement.Load(CurDir() & "\questions.xml")

注意,这里的survey变量并不是一个object对象,编译器由它右边的赋值表达式推断出是一个Xelement类型。下面我们使用...语法来选出所有的questionXelement,不管它在文件中的路径有多深。因为我们已经引用了schema,当我们在查询语句中输入"<"字符时,智能感知就会给出提示。然后我们遍历从查询语句中返回得到的Xelements的集合,从而创建我们的Question控件

Dim questions = From q In survey...<question> Select q

 

'We then add questions for each of the XElements

For Each question In questions

    Me.AddQuestion(question)

Next

现在我们需要修改动态类,引用前面说过的那些个命名空间,并且使其能够通过访问XElement对象中的元素来获得我们需要的值

Option Strict Off

Option Infer On

Imports System.Xml.Linq

Imports System.Linq

Imports <xmlns:ns="http://www.w3.org/2001/XMLSchema">

 

Public Class Dynamic

 

    ''' <summary>

    ''' Dynamically creates an object and sets properties on it

    ''' by reading elements on the passed in XElement object.

    ''' </summary>

    ''' <param name="info">An XElement with the following elements:

    ''' assembly

    ''' control

    ''' height

    ''' text

    ''' forecolor

    ''' backcolor</param>

    ''' <returns></returns>

    ''' <remarks>If these elements are not present a runtime error occurs.

    ''' In that case a multi-line Textbox is returned with the error message.</remarks>

    Shared Function GetQuestion(ByVal info As XElement) As Object

        Dim c As Object

        Try

            'VB does an automatic conversion at *runtime* when

            ' using these properties, however XML intellisense

            ' is available because we imported the schema above,

            ' which can enables safer dynamic programming.

 

            'This works because Option Strict is set to Off. In VB9,

            ' the default is to set Option Strict Off, but

            ' Option Infer ON. This gives static typing where possible,

            ' but dynamic typing where necessary. Since this is now

            ' project-wide, we could place this function directly into

            ' the form that uses it.

 

            c = System.Reflection.Assembly.Load(info.<assembly>.Value).CreateInstance(info.<control>.Value)

 

            c.Height = info.<height>.Value

            c.Text = info.<text>.Value

            'c.Text = info.<cool>.Value 'This will cause a runtime error

            c.ForeColor = Color.FromName(info.<forecolor>.Value)

            c.BackColor = Color.FromName(info.<backcolor>.Value)

 

            If TypeOf c Is TextBox Then

                c.ReadOnly = True

                c.MultiLine = True

                c.ScrollBars = ScrollBars.Vertical

            End If

 

        Catch ex As Exception

            'Try/Catch is required here, as this code will cause

            ' a runtime error if the XElement is missing fields

            ' or the type cannot be created.

            c = New TextBox

            c.Text = ex.ToString

            c.MultiLine = True

            c.Height = 100

            c.ReadOnly = True

            c.ScrollBars = ScrollBars.Vertical

        End Try

 

        Return c

    End Function

End Class

运行的时候,我们就会看到所有在XML 中的question都会被动态的显示在form上。我们使用的LINQ查询非常简单,并且只要我们在这个查询代码中加入适当的WhereOrder By,就能够很轻松的选出某一些我们需要的问题,或者按照我们的要求排序。既简单又文雅。

有很多方法可以创建静态的Question。在这儿,为了演示镶嵌的表达式,我们用LinqXLinq整合的方式来做。首先创建了一个QuestionInfo对象的数组(它只包含一个QuestionInfo对象),然后做一个基于这个数组的Linq查询,最后将这个查询语句的返回结果和上面我们写的XLinq查询语句合并

 

Dim survey = XElement.Load(CurDir() & "\questions.xml")

Dim qInfo As QuestionInfo() = {New QuestionInfo()}

 

Dim questions1 = From q In survey...<question> Select q

 

Dim questions2 = From info In qInfo _

                    Select <question>

                               <assembly><%= info.Assembly %></assembly>

                               <control><%= info.Control %></control>

                               <backcolor><%= info.BackColor %></backcolor>

                               <forecolor><%= info.ForeColor %></forecolor>

                               <text><%= info.Text %></text>

                               <height><%= info.Height %></height>

                           </question>

 

Dim allQuestions = questions1.Union(questions2)

 

For Each question In allQuestions

    Me.AddQuestion(question)

Next

现在我们就能得到和原来一样的界面,这次由于我们使用了XML的智能感知,动态编程就更简单了。

希望这篇文章能够给你提供一些关于在Visual Basic中如何利用动态编程的思路。这里VS 2005VS 2008的工程文件。

Enjoy! 

更新:点击这里,关于动态类的延伸。

posted on 2007-12-20 16:45:00 by VBCTI  评论(1) 阅读(2985)

在多行字符串中插入代码和避免下划线

[原文作者]Beth Massi

[原文链接]Embed Code and Avoid Underscores in Your Multiline Strings

 

当我从FoxPro转到使用VB.Net的时候,我最思念的事情之一就是在FoxPro中能够很容易地在编辑框中输入一束文字(多行字符串)和嵌入的代码(合并的文本)。对此,ForPro 有一个关键字“TEXT...ENDTEXT ”,我过去一直使用它。在VB中由于为了代码的可读性你不得不使用大量的下划线来连接文字串,结果就变得非常的糟糕。

  Dim oldWay = "this is a string" & vbCrLf & _

               "with formatting" & vbCrLf & _

               "and stuff" & vbCrLf & _

               "look ma, underscores" & vbCrLf & _

               "         tabs too"

 

  MsgBox(oldWay)

不再多说。使用带XML 文字支持的Visual Basic 9的版本,我们现在可以很容易的直接写入一束文字到编辑框中:

        Dim newWay = <string>

this is a string

with formatting

and stuff

look ma, no underscores!!!

            tabs too

                </string>

 

        MsgBox(newWay.Value)

文本格式也是被保护的。所有你需要做的就是得到可扩展元素,也就是字符串文字的值。现在你就可以看出,这比我们过去所使用的要更清楚。如果你依然喜欢用默认的微红色来看你的字符串文字,你可以在"工具-->选项-->环境-->文字和字体"里很容易地变更VB XML 文字的颜色设置,接着选择“VB XML 文本” 再定置颜色到RGB1632121)。这里是另外一个例子,一些SQL查询语句(现在使用的是默认的微红色):

        Dim query = <query>

SELECT Customers.*, Orders.OrderDate

FROM Customers

    INNER JOIN Orders ON Orders.CustomerID = Customers.CustomerID

WHERE Customers.CustomerID = @CustomerID

                    </query>

 

        Dim cmd As New SqlCommand(query.Value)

        cmd.Parameters.AddWithValue("@CustomerID", id)

现在是我们可以在哪里找到乐趣。你也可以使用“<%= 语句%>”插入表达式到这些文字里面.这就意味着有可以更清晰地做任何类型的文字合并,而不是单单进行代码和字符串的难看的串联,尤其是当文字长度增加的时候。下面是一些简单的例子:

        Dim simple = <string>

This is a simple text merge example:

Hello, <%= Environment.UserName %>

                     </string>

 

        MsgBox(simple.Value)

 

 

        Dim controls = <string>

There are the following controls on this form:

<%= From item In Me.Controls Select item.ToString & vbCrLf %></string>

 

        MsgBox(controls.Value)

Calvin有一些关于怎么动态生成脚本的好例子,例子1 例子2,但是下面就是使用一个简单代码生成模板的例子。

Private Sub CreateClass()

        Dim CustomerSchema As XDocument = XDocument.Load(CurDir() & "\customer.xsd")

 

        Dim fields = From field In CustomerSchema...<xs:element> _

                     Where field.@type IsNot Nothing _

                     Select Name = field.@name, Type = field.@type

 

 

        Dim customer = <customer>

Public Class Customer

    <%= From field In fields Select <f>    

        Private m_<%= field.Name %> As <%= GetVBPropType(field.Type) %></f>.Value %>

 

        <%= From field In fields Select <p>    

        Public Property <%= field.Name %> As <%= GetVBPropType(field.Type) %>

            Get

                Return m_<%= field.Name %> 

            End Get

            Set(ByVal value As <%= GetVBPropType(field.Type) %>)

                m_<%= field.Name %>  = value

            End Set

        End Property</p>.Value %>                       

End Class</customer>

 

        My.Computer.FileSystem.WriteAllText("Customer.vb", customer.Value, _

False, System.Text.Encoding.ASCII)

 

    End Sub

 

    Private Function GetVBPropType(ByVal xmlType As String) As String

        Select Case xmlType

            Case "xs:string"

                Return "String"

            Case "xs:int"

                Return "Integer"

            Case "xs:decimal"

                Return "Decimal"

            Case "xs:boolean"

                Return "Boolean"

            Case "xs:dateTime", "xs:date"

                Return "Date"

            Case Else

                Return "'TODO: Define Type"

        End Select

    End Function

我希望这些能够带给你在怎么处理XML 文字方面一些好的想法。你不需要使用它只能生成XML文件,你可以用它来生产任何基于文本格式的文件。

posted on 2007-12-13 10:25:00 by VBCTI  评论(0) 阅读(4339)

使用LINQ to XML

[原文作者]Beth Massi

[原文链接]Getting Started with LINQ to XML

 

正如Rob指出的(实际上,我今天在Dev Center上看到了他的帖子),这里有一些关于Visual Studio 2008教程视频。其中一个就是关于LINQ to XML的概览(不过很抱歉,我并不能给出该视频的直接的链接,因为它被掩盖在精巧的Silverlight控件之下,滚动下视频自己找找吧) Chris Pels 给出了一个很好的XML programming介绍,但仍有两点个人非常喜欢的地方遗漏掉了(他们是内嵌表达式XML智能感知)。这两个特性也非常易于使用。所以,请先看视频然后再回来,我将带你们浏览下面的内容。

 

内嵌表达式(Embedded Expression

这里,我们通过直接复制XML文本,创建一个XDocument对象

Dim contactlist1 = <?xml version="1.0" encoding="utf-8"?>

                   <contacts>

                       <contact>

                           <lastname>Davolio</lastname>

                           <firstname>Nancy</firstname>

                           <state>WA</state>

                           <phone>(206) 555-9857</phone>

                       </contact>

                       <contact>

                           <lastname>Buchanan</lastname>

                           <firstname>Steven</firstname>

                           <state>CA</state>

                           <phone>(925) 555-4848</phone>

                       </contact>

                       <contact>

                           <lastname>Suyama</lastname>

                           <firstname>Michael</firstname>

                           <state>CA</state>

                           <phone>(925) 555-7773</phone>

                       </contact>

                       <contact>

                           <lastname>Callahan</lastname>

                           <firstname>Laura</firstname>

                           <state>WA</state>

                           <phone>(206) 555-1189</phone>

                       </contact>

                   </contacts>

有趣的是,你可以通过<%=%>创建内嵌表达式,并且任何VB代码都可以放在这里。下面的一个例子将把现在时间放在lastUpdated的属性里

Dim contactlist2 = <?xml version="1.0" encoding="utf-8"?>

                   <contacts lastUpdated=<%= Now() %>>

...

                   </contacts>

内嵌表达式有各种各样的使用方式。你还可以通过内嵌LINQ查询(比如LINQ to SQL)来创建一个XML对象。 下面的例子将展示如何将Employee表中的雇员信息查询出来并建立一个XDocument对象

Dim contactlist3 = <?xml version="1.0"?>

                   <contacts>

                       <%= From employee In db.Employees _

                           Select <contact>

                                      <lastname><%= employee.LastName %></lastname>

                                      <firstname><%= employee.FirstName %></firstname>

                                      <state><%= employee.Region %></state>

                                      <phone><%= employee.HomePhone %></phone>

                                  </contact> %>

                   </contacts>

但真正让我最为喜爱的是,实际上你可以很容易的将任何XML片断改造成为内嵌LINQ to XML片断。我们因此再也不需要XSLT了!(感谢上帝,因为每一次我回顾我的模板以及样式表时,都需要花费我打量时间来回忆这到底在写些什么。我讨厌XSLT,因为他们并不直观,至少对我来说是)

 

然而,在VB中,转型却非常直观。因为逻辑上他们是自上而下的,而且没有任何需要记忆API调用。下面的示例,展示我们可以通过改写上面的contactlist1(or contactlist3)来建立不同的XML文档。让我们看看如果我只想要California contacts 的情况:

Dim contactlist4 = <?xml version="1.0" encoding="utf-8"?>

                   <contacts>

                       <%= From contact In contactlist1...<contact> _

                           Where contact.<state>.Value = "CA" _

                           Select contact %>

                   </contacts>

这将生成如下的XML文档:

<?xml version="1.0" encoding="utf-8"?>

<contacts>

  <contact>

    <lastname>Buchanan</lastname>

    <firstname>Steven</firstname>

    <state>CA</state>

    <phone>(925) 555-4848</phone>

  </contact>

  <contact>

    <lastname>Suyama</lastname>

    <firstname>Michael</firstname>

    <state>CA</state>

    <phone>(925) 555-7773</phone>

  </contact>

</contacts>

或者我们可以将其改为一种新的结构:

 

Dim contactlist5 = <?xml version="1.0" encoding="utf-8"?>

                   <employees>

                        <%= From contact In contactlist1...<contact> _

                            Where contact.<state>.Value = "CA" _

                            Select <employee>

                                      <%= contact.<lastname> %>

                                      <%= contact.<firstname> %>

                                      <region><%= contact.<state>.Value %></region>

                                   </employee> %>

                   </employees>

这将生成如下的XML文档:

<?xml version="1.0" encoding="utf-8"?>

<employees>

  <employee>

    <lastname>Buchanan</lastname>

    <firstname>Steven</firstname>

    <region>CA</region>

  </employee>

  <employee>

    <lastname>Suyama</lastname>

    <firstname>Michael</firstname>

    <region>CA</region>

  </employee>

</employees>

你甚至可以选取片断并做任何你想要的事情,或者合并他们。一个片断就是一个XElement

Dim contactlist6 = <contacts>

                       <contact>

                           <lastname>Massi</lastname>

                           <firstname>Beth</firstname>

                           <state>CA</state>

                           <phone>(925) 555-1212</phone>

                       </contact>

                   </contacts>

我们可以很容易的将其并如其它片断中,如:

Dim contactlist7 = <CApeople>

                       <contacts>

                           <%= From contact In contactlist1...<contact> _

                               Where contact.<state>.Value = "CA" _

                               Select contact %>

                       </contacts>

                       <beth>

                           <%= From beth In contactlist6...<contact> _

                               Select beth.<lastname> %>

                       </beth>

                   </CApeople>

其结果如下:

<CApeople>

  <contacts>

    <contact>

      <lastname>Buchanan</lastname>

      <firstname>Steven</firstname>

      <state>CA</state>

      <phone>(925) 555-4848</phone>

    </contact>

    <contact>

      <lastname>Suyama</lastname>

      <firstname>Michael</firstname>

      <state>CA</state>

      <phone>(925) 555-7773</phone>

    </contact>

  </contacts>

  <beth>

    <lastname>Massi</lastname>

  </beth>

</CApeople>

 

好了,我想你可能会想,“这太神奇了!”(至少我希望如此)。但是别急,还有更多

 

XML智能感知(XML IntelliSense

 

如果没有了智能感知Visual Basic将会怎么?即使是XML,在VB中仍旧可以提供智能感知,但是无论如何,你不得不事先告诉VB,你正在处理的XML数据的Schema。要这样做,你只需将你的XML文档添加到项目中,然后在XML菜单中选择“Create Schema.

 

 

这将通过推测Schema,生成一个XSD文件。然后你需要找到这个文件,然后将其添加到你的项目中。这个灵巧的小工具叫XML to Schema Tool,你可以下载并装入一个叫做”XML to Schema”New Item模版。这样可以让这一切更简便些。它可以从网页中的XML资源,粘贴下来的xml 文本,或者磁盘中的xml文件中推测schema,然后自动将生成的XSD文件添加至你的项目中,这一切只需要一步操作。

现在我们的得到了schema,我们可以XML中,键入代码来得到智能感知的帮助了:

 

 

Visual Basic还可以通过在代码顶部添加Imports语句,来处理XML 命名空间。这样你可以在一个项目中处理多个命名空间。

 

好了,到这里我希望已经给了你们一个关于VBXML的很好的介绍。关于这个新语言特性,真的有太多的事情可以做了,所以并没能将他们一一的列写出来。期待着推出更多的教程视频

 

Enjoy

-B

posted on 2007-12-11 10:43:00 by VBCTI  评论(3) 阅读(3984)

让我们的代码更动态

[原文作者]Beth Massi

[原文链接]Making our Code More Dynamic

在前一篇文章中,我演示了如何动态的创建一个基于XML的用户UI界面,可是在那个程序里,因为是我们自己写的那个xml文件,所以我们早就知道了想要操作的是哪些属性。现在我想对原先的那个示例做一些扩展,对于那些我们动态创建的对象也能通过从XML中读取其属性名称的方法,来动态的对这些属性赋值。

为了能够动态的获取和设置属性,我们需要重写Dynamic类。在这儿,我们用到了函数CallByName(这和VFP中的SetProperty()方法类似)。这个函数在很多场合很有用,可惜的是,目前它只能应用于简单类型,所以我们无法用它来动态的设置颜色(Color类型)。请看以下代码:

我修改过的XML文件是这样的:

<?xml version="1.0" encoding="utf-8" ?>

<questions>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the first survey question.</text>

    <height>35</height>

    <readonly>true</readonly>

    <tabstop>false</tabstop>

    <multiline>true</multiline>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the second survey question.</text>

    <height>100</height>

    <readonly>true</readonly>

    <tabstop>false</tabstop>

    <multiline>true</multiline>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.Label</control>

    <text>This is the third survey question.</text>

    <height>80</height>

    <tabstop>false</tabstop>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.Button</control>

    <text>This is the fourth survey question.</text>

    <height>30</height>

    <tabstop>false</tabstop>

  </question>

</questions>

 

现在我们只从文件中读出一列属性(Properties)。这个可以用VB8DataSet轻松做到:

'Read the xml into a dataset for easier processing of the elements

Dim myData As New DataSet()

myData.ReadXmlSchema(CurDir() & "\questions.xsd")

myData.ReadXml(CurDir() & "\questions.xml")

Dim survey As DataTable = myData.Tables(0)

 

'Read the properties from the XML

Dim props As New List(Of String)

For Each dc As DataColumn In survey.Columns

    props.Add(dc.ColumnName)

Next

'Now add all the questions defined in the questions.xml file

For Each row As DataRow In survey.Rows

    Me.AddQuestion(row, props)

Next

现在我们将这一列属性(properties)info对象一起传入DynamicGetQuestion方法,这里的info对象既可以是一个DataRow也可以是我们之前创建的静态的QuestionInfo对象。注意我并没有在前面更改过QuestionInfo类,下面这段代码会忽略任何在info对象中无法找到的属性或者无法通过CallByName赋值给cc就是在下面代码中我们动态创建的控件)的属性。这就可以使代码更加动态化,因为现在,我们并不必要知道我们创建对象的类型,也不必知道需要被设置的属性: 

Option Strict Off

 

Public Class Dynamic

 

    ''' <summary>

    ''' Dynamically creats an object and sets properties on it

    ''' by reading properties on the passed in object.

    ''' </summary>

    ''' <param name="info">An object with the property values</param>

    ''' <param name="properties">The list of properties to set from the info

    ''' object onto the created object</param>

    ''' <returns></returns>

    Shared Function GetQuestion(ByVal info As Object, ByVal properties As List(Of String)) As Object

        Dim c As Object

        Dim propValue As Object

        Try

 

            c = System.Reflection.Assembly.Load(info!Assembly).CreateInstance(info!Control)