如何用Lambda表达式来写VS2010扩展工具

[原文作者]:Lucian

[原文链接]:How to write a VS2010 Extension using Statement Lambdas

        以下是如何在Visual Basic写VS2010扩展工具(“.vsix”)。该代码变得更加容易,归功于VisualBasic10.0的诸多新功能之一,“lambda表达式”。

  • 这一扩展使得注释使用新的字体,漫画字体并加粗斜体。
  • 你所写的扩展工具可以将任何WPF的图形化效应放在缓冲区。
  • [我做了一个Channel9频道的视频来解释这篇博客,不过它还在制作中….]
  • 源代码: ComicComments – src.zip [9k]
  • 预建的二进制: ComicComments.vsix [26k]

image

0. 准备阶段

        从这里安装VS2010 SDK: http://www.microsoft.com/downloads/details.aspx?FamilyID=47305cf4-2bea-43c0-91cd-1b853602dcc5&displaylang=en

        关于它的MSDN文档是在这里: http://msdn.microsoft.com/en-us/library/bb166441(VS.80).aspx

1. 创建项目

文件“>新建”>项目“>扩展> EditorClassifier

这就创建了一个新的项目。它创造了三个源文件:

– EditorClassifier1.vb

– EditorClassifier1Format.vb

– EditorClassifier1Type.vb

这比我们需要的要更为复杂一点。我删除了该项目的最后两项,并用下面的代码替代EditorClassifier1.vb里面的代码:

Imports System.ComponentModel.Composition

Imports System.Windows

Imports System.Windows.Media

Imports Microsoft.VisualStudio.Text.Classification

Imports Microsoft.VisualStudio.Text.Editor

Imports Microsoft.VisualStudio.Utilities

<Export(GetType(IWpfTextViewCreationListener)), ContentType("code"), TextViewRole         (PredefinedTextViewRoles.Document)>

Class ViewCreationListener : Implements IWpfTextViewCreationListener

<Import()> Dim formatMapService As IClassificationFormatMapService

<Import()> Dim typeRegistry As IClassificationTypeRegistryService

Public Sub TextViewCreated(ByVal view As IWpfTextView) Implements IWpfTextViewCreationListener.TextViewCreated

End Sub

End Class

<Export>和的<Import>属性表明了扩展是一个MEF组件。它的长短取决于它的代码TextViewCreated会被每个新的代码编辑器缓冲调用一次。

2.更改格式:第一次尝试

下面是第一次尝试实现TextViewCreated:

Public Sub TextViewCreated( ByVal view As IWpfTextView ) Implements IWpfTextViewCreationListener .TextViewCreated Public Sub TextViewCreated(ByVal view As IWpfTextView) Implements IWpfTextViewCreationListener.TextViewCreated

Dim formatMap = formatMapService.GetClassificationFormatMap(view)

Dim textClassification = typeRegistry.GetClassificationType("text")

Dim textProperties = formatMap.GetTextProperties(textClassification)

Dim commentClassification = typeRegistry.GetClassificationType("comment")

Dim commentProperties = formatMap.GetTextProperties(commentClassification)

Dim commentTypeface2 = New Typeface(New FontFamily("Comic Sans"), FontStyles.Italic, FontWeights.Bold, commentProperties.Typeface.Stretch)

Dim commentEmSize2 = textProperties.FontRenderingEmSize + 1

Dim commentProperties2 = commentProperties.SetTypeface(commentTypeface2).SetFontRenderingEmSize(commentEmSize2)

formatMap.SetTextProperties(commentClassification, commentProperties2)

End Sub

        每种语言把缓冲区中的文本进行分类,作为“注释”或“文本”或“类型名称”。该formatMap是一个类别到字体的映射。黄线那一行就是我们更新映射的地方。

3.更改格式映射:最后一次尝试

        上述代码有一些问题。第一个问题是,虽然TextViewChanged可以调用不止一次,但代码中黄线那一行所花出的代价是相当昂贵的– 它会导致整个UI的刷新。要想能避免这种情况就只能改变映射如有必要的话:

If Not commentProperties.Equals(commentProperties2) Then

formatMap.SetTextProperties(commentClassification, commentProperties2)

End If

        第二个问题是,有时TextViewChanged没有在适当的时候被调用。比如,可能调用过早了。为了使它更为强健,我们在首次窗口获得焦点时还应该更新formatMap,以响应view.GotAggregateFocus事件。

        第三个问题是,如果一些其他插件对formatMap进行了更改,或者如果用户改变,例如字体大小,那么我们需要对formatMap重做。正确的方法是响应formatMap.ClassificationFormatMappingChanged事件。

        最后一个问题是,如果我们通过调用formatMap.SetTextProperties响应        formatMap.ClassificationFormatMappingChanged事件那本身可能会再次触发formatMap.ClassificationFormatMappingChanged事件!因此,我们必须防止自己递归地调用。

        所有这些问题都可以用多行的lambda表达式优雅的解决了。这就是:

Public Sub TextViewCreated(ByVal view As IWpfTextView) Implements IWpfTextViewCreationListener.TextViewCreated

Dim formatMap = formatMapService.GetClassificationFormatMap(view)

Dim inUpdate = False

Dim FixComments =

Sub()

        If inUpdate Then Return Else inUpdate = True

        Dim textClassification = typeRegistry.GetClassificationType("text")

        Dim textProperties = formatMap.GetTextProperties(textClassification)

        Dim commentClassification = typeRegistry.GetClassificationType("comment")

        Dim commentProperties = formatMap.GetTextProperties(commentClassification)

        Dim commentTypeface2 = New Typeface(New FontFamily("Comic Sans"), FontStyles.Italic, FontWeights.Bold,         commentProperties.Typeface.Stretch)

        Dim commentEmSize2 = textProperties.FontRenderingEmSize + 1

        Dim commentProperties2 = commentProperties.SetTypeface(commentTypeface2).SetFontRenderingEmSize                (commentEmSize2)

            If Not commentProperties.Equals(commentProperties2) Then

            formatMap.SetTextProperties(commentClassification, commentProperties2)

            End If

        inUpdate = False

    End Sub

    AddHandler view.GotAggregateFocus, FixComments

AddHandler formatMap.ClassificationFormatMappingChanged, FixComments

FixComments()

End Sub

        黄色突出显示的地方就是关键的变化。

        用Lambda的好处就是我们能够保留一切本地化的东西到它需要的地方。这是因为FixComments lambda能够直接使用各个字段( ViewformatMapinUpdate)。如果我们想把FixComments变成一个正常命名的方法,那么我们就必须新建一个类,而这个类里有那三个东西作为成员,而且还要为它写一个构造函数,这个时候代码就开始看着有点混乱了。

 4.调试和部署

        source.extensions.vsixmanifest -这个文件包含有关插件的名字及其他一些信息。你必须提供一些附加的文件:

                – 某个你选择的“license.txt“

                – 一个图标32×32像素大(我用PNG格式)

                – 另一个图标200×200像素大(我还是使用PNG)

        要调试,只需像平时一样按F5键。这将启动安装了扩展的VS2010版本。然而,它是安装到一个叫“Experimental”文件夹

C:\Users\lwischik\AppData\Local\Microsoft\VisualStudio\10.0Exp

而不是更常见的

C:\Users\lwischik\AppData\Local\Microsoft\VisualStudio\10.0
用命令行参数“/ rootsuffix Exp”启动VS,所有新编辑分类项目已建立,这就是为什么它在experimental文件夹。这是为了让您可以不用妥协真的VS就可以调试。

        一旦完成之后,您就可以部署了。这个扩展打包在EditorClassifier1 \bin\ Debug文件夹里名为

– EditorClassifier1.vsix

任何用户可以双击此.vsix文件安装该扩展。在内部,它实际上只是一个.zip文件,里面包含必要的文件。

附件: clip_image001ComicComments-src.zip

Debugger下, 无处藏私

[原文作者]:Jared Parsons

[原文链接]:Nothing is private in the debugger

        Debugger的目标是为process提供丰富功能审查 (inspection capabilities) 。审查的主要方法是通过对文本表达式(textual expressions)进行求值,这是由表达式计算器可识别的特殊语言组建控制的。该组件为调试器的部分窗口提供数据(如监视器,区域变量,自动化等等…)

        当debugger的时候,如果表达式在文件中断点位置, 表达式计算器不遗余力的去确保调试器中的表达式的值像期望的一样精确。否则会导致用户困惑, 通常会更难追查正在调试的问题,导致对调试器和语言质量信心的丧失。

        偶尔,或不那么偶然,提供高真实性的求值(evaluation)和广泛的审查(inspection)会产生矛盾。如果发生这种情况,我们必须根据用户的混乱来权衡,改变VS语言的语义(semantics),提升调试器的性能(capabilities)。

        以可存取性(Accessibility)作为例子。语言有强烈的可存取性(Accessibility) 概念, 这是是由编译器和CLR强力执行的。为了提供广泛的审查,调试器必须能够访问所有可用的数据,包括语言不能进入的。否则,开发者解决手头的问题需要用到的数据可能被隐藏。因此,可存取性(Accessibility)是表达式计算器屈服于语言规则,并允许访问没有存取性(accessibility)检查的数据。

        表面上这似乎不是一个大的变化。这是一种细小的改变,用户能看到更多的fields,properties和methods。在大多数情况下,这很简单,却容易引发混乱。

        然而,看似简单的改变,例如删除accessibility checks,可能对用户导致非常奇怪的行为。最近我们的QA团队提交了一个bug,说明了这一点。

Module Module1

    Class Base

        Private Field1 As Integer = 55

    End Class

    Class Derived

        Inherits Base

        Sub Method1()

            Field1 = 72

            Stop

        End Sub

    End Class

        Sub Main()

        Dim x As New Derived

        x.Method1()

        End Sub

End Module

Module Module2

    Public Field1 As Integer

End Module

        QA注意到,如果运行这段代码,在监视窗口中键入field1显示的不是72而是55。这是一个bug,因为field1的值在前一行被设置成72。

        这种行为非常有趣,虽然十分混乱,实际上是by design的。

        原因是,debugger是对表达式的求值,一切都是公开的。这意味着,是在公共的instance中计算Method1中field1的值。正确的绑定是Base::field1,因为在instance fields和Module fields都可以访问时, VB语言更倾向instance fields。正确的表达式计算器的计算结果为55。

        然而,当代码编译伴随可存取性检查。这意味着Base::field1不被考虑,因为它是私有的和不可存取的 (inaccessible) 。编译器没有正确绑定到Module2::field1,这就是代码正在运行时正在被使用的域(field)。开发人员可以在调试器窗口中通过计算Module2.Field1的值来验证这一点。

调试器中所有的变量都可以访问(Part-2)

[原文作者]:Jason Zanders

[原文链接]:Nothing is private in the debugger (part 2) – jaredpar’s WebLog – Site Home – MSDN Blogs

        在之前的章节中,我们讨论了在调试器中,识别表达式值的时候,怎样才能跳过表达式的访问属性的约束以及识别表达式时产生的一些不可预期的情况。有一种情况在上篇文章中,我忘了提到,那就是识别程序是怎样与Vb后期绑定引擎一起工作的。

        当绑定一个表达式时,表达式识别器放宽可访问性规则的检查限制。这是因为表达式识别器可以有效地承载编译器,以及可以重写一些项目像访问性检查。

        至于后期绑定的编译器,只是参与表达式编译,只有在vb程序运行时,才会调入后期绑定引擎。

        可访问性决定的后期绑定调用方式调用的的变量会在vb程序运行时进行检查,并且不能被表达式识别器重写。

        后期绑定访问与静态访问相结合,会导致其它的混乱行为。例如。

Class C1

    Private Field1 As Integer

    Public Property Property1 As Integer

End Class

Module Module1

    Sub Main()

        Dim v1 As New C1

        Dim v2 As Object = v1

        Stop

    End Sub

End Module

        当代码运行时,v1中所有成员都可以访问。V1对于C1是静态类型,因此表达式识别器可以忽略它的访问属性,获得它的值。

image

        v2引用同一个对象实例,所以我们可以合理假设它可以获得相同的值。不过由于它是静态类型的对象,需要像V1.Field1一样调用,实际上调用时变成后期绑定调用,因此同样受到后期绑定规则的约束。

image

        这里会识别Field1失败因为后期绑定器不允许访问私有成员。Property1显示正常结果,因为它是公有成员,我们正在考虑在将来发布的产品中改变这种调用方式,一如既往,无需承诺。

DataRepeater 虚拟模式的使用

[原文作者]:John Chen

[原文链接]:Using the DataRepeater Virtual Mode

        当在论坛中回答问题的时候,我经常会遇到一个问题,虚拟模式是如何与包含了Visual Basic Power Packs的DataRepeater控件一起工作的?我看到并不是很多人在使用这个强大的功能,所以我再次讨论它是如何工作的,然后通过一个实例来使用它。

        DataRepeater有一个VirtualMode属性,它是做什么用的?

        让我从下面的DataRepeater控件的工作机制图来开始。

clip_image001

                                            图1

         当一个程序启动时,数据从数据库加载到内存中的结构化数据高速缓存(如数据集)。数据高速缓存通过数据绑定机制(如DataRepeater.DataSource = BindingSource)绑定到视图(这里的DataRepeater)。用户通过DataRepeater与数据交互。用户可以滚动视图以显示不同的数据行和修改在不同行的数据。原始数据从数据缓存取出,在视图中显示;修改后的数据又放回到数据缓存中去。只要我们设置数据绑定渠道的属性(像DataRepeater.DataSource = BindingSource),这一切都自动发生。

       使用虚拟模式意味着你不愿使用数据绑定机制,你因为各种原因而想为DataRepeater控件提供你自己的数据管理操作。例如,你可能要提供正在使用的数据,这样你就没有一个可以为DataRepeater用作一般数据源的数据缓存。

       性能是人们选择使用虚拟模式的主要原因之一,尤其是显示大量数据的时候,在DataRepeater控件中,就好像你在控制加载数据并且可以最优化加载速度和内存消耗。

        请注意,性能是一个复杂的问题,它可以被很多因素影响。使用虚拟模式并不能保证你有最佳的结果。例如,如果所有的数据行已经被加载到内存中,像充满TableAdapter.Fill方法的DataSet,你可能不会有虚拟模式的性能增益。

       源于论坛最新的关于DataRepeater虚拟模式用法的问题,接下来,我将给出一个实例来重点介绍如何使用虚拟模式。

约会薄实例

       我的例子是约会薄。它允许你创建电子日程,这个电子日程可以设置在自定义的一周范围内。这是我的新年决心,以更好的管理(开个玩笑)。

       约会薄的界面显示如下:

clip_image002

                                                 图2

        左边是一个每行7天的日历,右边在日历中一行一天地显示约会详情。约会列表页可以像下图右边所示显示成一列。

clip_image003

                                                        图3

         要导航到指定的一周,可以点击左侧面板上的星期,相关的日期就会在右侧面板依次显示出来,然后点击一个指定的日期,在TextBox里填上约会的详细描述,相关日期的颜色就会变成紫色。要删除一个约会,删除文本框中的内容。切换到约会列表视图里,可以很清楚的看到需要关注的约会。

       我用一个简单的数据集来代表日程表。我在这里假定你对DataSet类很熟悉,这个DataSet包含2个表:AppointmentBook和Appointment,如下所示:

clip_image004

                                                 图4

        表AppiontmentBook只包含一个记录用来定义日历的开始日期(必须是星期日)和日历上星期的总数。表Appointment包含约会的日期(以天
为代表从开始日期偏移)和约会的详细信息。对一个特定日期只允许一个约会。我承认这是一个过于简单的数据模型,但是它让我们把目光更多的集中在虚拟模式的使用上,更少关注程序的细节。

该软件允许你创建一个新的约会薄或打开已有的一个。经过应用程序的设置,最后一次打开的文件会被记住。

实例中的DataRepeaters

        在程序中,我用了3个DataRepeaters,都用了虚拟模式。第一个叫WeekCalendarDataRepeater,它是用来表示左边的日历(见图2)。我不会在这个DataRepeater上做任何的数据输入,数据值(字符串值12/27,12/28,etc.)是动态计算的,因此没有必要通过一个数据缓存类来缓存这些值。这是使用虚拟模式的完美例子。在这个例子中,我将说明怎样提供数据给DataRepeater.

        第二个叫DayCalendarDataRepeater,它用来在日历视图中显示和编辑约会信息(图2右手边)。我已经将DataRepeater设置成固定范围,每一行代表一个日期,因此它不会允许你添加或者删除项目(在这里即行)。我将演示如何把数据从DataRepeater取出并存进数据缓存AppointmentDataTable中。你会发现DataRepeater的项和AppointmentDataTable的行并不是一个一对一的关系。Appointment可以由更少的项。这是另一个说明使用VirtualMode比非VirtualMode更合适的好例子。

       第三个DataRepeater叫ApptListDataRepeater,用于以列表显示约会(图3,右手边)。对这一个,没有必要对AppointmentDataTable使用VirtualMode设置DataRepeater.DataSource。然而,我仍然使用VirtualMode来说明添加和删除项的应用。

DataRepeaters的详细应用

       要正确使用DataRepeater,你需要设置DataRepeater.VirtualMode为true,设置DataRepeater.ItemCount并处理以下四项活动:ItemValueNeeded,ItemValuePushed,NewItemNeeded 和ItemsRemoved。让我们用以个例子来深入到细节。

1) 要使用DataRepeater VirtualMode,首先要做的是设置DataRepeater.VirtualMode属性为true。你可以通过属性浏览器或者像如下Form_Load事件处理那样在代码里设置。我将我所有的三个DataRepeater设置成VirtualMode。(部分代码在函数中删除,以提高可读性,其他代码段也是如此。我将在最后提供完整的样本衔接。)

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles MyBase.Load

‘ Setup the Virtul Mode state for the DataRepeaters

    Me.WeekCalendarDataRepeater.VirtualMode = True

    Me.DayCalendarDataRepeater.VirtualMode = True

    Me.ApptListDataRepeater.VirtualMode = True

End Sub

2) 然后你需要告诉DataRepeater最初有多少项。在实例中,三个DataRepeater 有总周数,天数和约会数在OpenAppointmentBook函数里。

Private Sub OpenAppointmentBook()

    apptDataSet = New AppointmentDataSet()

    apptDataSet.ReadXml(dataFile)

    Dim row1 As AppointmentDataSet.AppointmentBookRow = _

    CType(apptDataSet.AppointmentBook.Rows(0), AppointmentDataSet.AppointmentBookRow)

    day0 = row1.StartDate

    totalWeeks = row1.TotalWeeks

    totalDays = totalWeeks * 7

    Me.WeekCalendarDataRepeater.ItemCount = totalWeeks

    Me.DayCalendarDataRepeater.ItemCount = totalDays

    Me.ApptListDataRepeater.ItemCount = apptDataSet.Appointment.Rows.Count

End Sub

3) 不管DataRepeaterItem说明时候需要,你都提供数据给它(例如,当滚动查看项的时候)。这通过ItemValueNeeded事件。例如,在WeekCalendarDataRepeater,我通过计算约会数和周数来为Label提供值。

Private Sub WeekCalendarDataRepeater_ItemValueNeeded(ByVal sender As Object, ByVal e As _

DataRepeaterItemValueEventArgs) Handles WeekCalendarDataRepeater.ItemValueNeeded

    Dim i As Integer = e.ItemIndex

    If (i >= 0 AndAlso i < TotalWeeks) Then

      Select Case e.Control.Name

        Case "Label1"

             e.Value = GetDate(i, 0)

        Case "Label2"

             e.Value = GetDate(i, 1)

        Case "Label3"

             e.Value = GetDate(i, 2)

        Case "Label4"

             e.Value = GetDate(i, 3)

        Case "Label5"

             e.Value = GetDate(i, 4)

        Case "Label6"

             e.Value = GetDate(i, 5)

        Case "Label7"

             e.Value = GetDate(i, 6)

        Case "WeekLabel"

             e.Value = i.ToString()

      End Select

   End If

End Sub

我没有在WeekCalendarDataRepeater中处理ItemValuePushed事件,所以它只读。

4) 当DataRepeaterItem在DataRepeater更新,你需要处理ItemValuePushed事件来保存数据。例如,在DayCalendarDataRepeater,我们收到来自DataRepeater的ApptTextBox,它存储用户刚输入的或者编辑的约会描述。我们把值存储在数据集中。

Private Sub DayCalendarDataRepeater_ItemValuePushed(ByVal sender As Object, ByVal e As _

DataRepeaterItemValueEventArgs) Handles DayCalendarDataRepeater.ItemValuePushed

    Dim i As Integer = e.ItemIndex

       If (i >= 0) Then

        Select Case e.Control.Name

          Case "ApptTextBox"

             UpdateDataSet(i, CType(e.Value, String))

       End Select

       End If

End Sub

我将WeekCalendarDataRepeater和DayCalendarDataRepeater连同AllowUserToAddItems和AllowUserToDeleteItems设置成false(见图5)所以项数是固定的,我并不需要处理NewItemNeeded和ItemsRemoved事件。

clip_image005

图5

5) 在AppListDataRepeater,我需要添加和删除约会。我使用AddButton和RemoveButton来执行添加或删除操作。点击AddButton的代码如下所示。主要的语句是ApptListDataRepeater.AddNew()。添加了错误处理代码以便如果发生什么错误它不会添加一个新行。

Private Sub ApptListDataRepeater_AddButton_Click(ByVal sender As System.Object, ByVal e As _

System.EventArgs) Handles AddButton.Click

    Dim oldCount = ApptListDataRepeater.ItemCount

    Try

      ApptListDataRepeater.AddNew()

   Catch er As Exception

     If (er.GetType.FullName <> GetType(CancleException).FullName) Then

       MessageBox.Show(er.Message)

     End If

‘ Remove the last added row

     If (ApptListDataRepeater.ItemCount > oldCount) Then

       ApptListDataRepeater.ItemCount = oldCount

       Me.TotalApptsLabel.Text = apptDataSet.Appointment.Rows.Count.ToString()

     End If

    End Try

End Sub

上面的代码将触发DataRepeater执行NewItemNeeded事件。我们如下处理:

Private Sub ApptListDataRepeater_NewItemNeeded(ByVal sender As Object, ByVal e As System.EventArgs) _

Handles ApptListDataRepeater.NewItemNeeded

‘Raise a dialog to get a date an appoint

      Dim appointmentForm As New NewAppointmentForm

      If (appointmentForm.ShowDialog(Me) = Windows.Forms.DialogResult.OK) Then

        Me.apptDataSet.Appointment.AddAppointmentRow(appointmentForm.AppointmentDate.Subtract(day0).Days, _

             appointmentForm.Description)

        RefreshDataRepeater(Me.DayCalendarDataRepeater)

        RefreshDataRepeater(Me.WeekCalendarDataRepeater)

     Else

       Throw New CancleException

     End If

End Sub

我使用一种新的形式输入日期和详细信息用以构成一个新的约会数据项。

6) 要删除一个项,你可以使用DataRepeater.RemoveAt(index)如下:

Private Sub ApptListDataRepeater_DeleteButton_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles DeleteButton.Click

       ApptListDataRepeater.RemoveAt(ApptListDataRepeater.CurrentItemIndex)

End Sub

你还可以使用删除键达到同样的效果。同时,你需要处理ItemsRemoved事件如下:

Private Sub ApptListDataRepeater_ItemsRemoved(ByVal sender As Object, ByVal e As _

DataRepeaterAddRemoveItemsEventArgs) Handles ApptListDataRepeater.ItemsRemoved

       If (e.ItemIndex >= 0 AndAlso e.ItemIndex < apptDataSet.Appointment.Rows.Count) Then

           apptDataSet.Appointment.Rows.RemoveAt(e.ItemIndex)

       End If

End Sub

7) 最后,在我的例子中,你可能会看到我有一个函数来刷新DataRepeater。

Private Sub RefreshDataRepeater(ByRef repeater As DataRepeater)

        Debug.Assert(repeater IsNot Nothing, "repeater is not set")

        repeater.BeginResetItemTemplate()

        repeater.EndResetItemTemplate()

End Sub

当你想要DataRepeater来刷新数据的时候,例如这个例子中的DataSet,你会需要这个。请注意,DataRepeater.Invalidate是用来强行控制重绘,它不会更新数据。

小结

这里是一个对DataRepeater VirtualMode使用的小结:

1) 设置 DataRepeater.VirtualMode = true

2) 设置DataRepeater.ItemCount.

3) 处理DataRepeater.ItemValueNeeded事件来发布DataRepeater

4) 如果数据变化,调用DataRepeater.BeginResetItemTemplate和EndResetItemTemplete来刷新DataRepeater

5) 处理ItemValuePushed来保存DataRepeater中的变化

6) 调用DataRepeater.AddNew来添加一个新项并处理NewItemNeeded事件以提供新的值

7) 调用DataRepeater.RemoveAt(index)来删除一个DataRepeaterItem并处理ItemsRemoved事件来从外部数据存储中删除值。

有关完整的实例代码,请在MSDN Code Gallery里check out

http://code.msdn.microsoft.com/AppintmentBook

有关DataRepeater虚拟模式的更多信息,请参照此处的MSDN文档。

在Insert操作中刷新主键识别列

[原文作者]:John Chen

[原文链接]:Refresh the Primary Key Identity Column during Insert Operation

        如果你在数据表中定义了主键标识列,当你在这个表中插入新行时,数据库引擎会自动设置它的值。这个标识值是由列的标识种子和标识递增的属性决定的。

        在客户端应用中,你可以在对应的ADO.NET 数据表中插入新行(数据表可以被看做数据库在缓存中的数据表,在本文中我会用数据表举例说明)。

        在这篇文章中,我将描述一下利用VS 数据库工具如何检索ADO.NET应用程序中的标识值。用Windows Forms Application DataSet举例,数据库服务器是SQL Server.

        首先在VS(2005或者之后的版本)服务器资源管理器中建立和一个SQL Server数据库的连接。创建一个名称为MyCustomer的表,包含三列:CustId, Name和Company. CustId被定义为主键和标识列。标识种子和标识递增为缺省值1,如下表:

clip_image001

图1 数据库中定义的MyCustomer 表

        然后我通过数据源配置向导创建DataSet,或者可以增加一个DataSet,然后将MyCustomer从服务器资源管理器拖到DataSet设计器上面。打开DataSet,学则CustId列,你将会看到它有以下的属性:AutoIncrement=True, AutoIncrementSeed = -1 , AutoIncrementStep = -1 (图2)。这些属性会被ADO.NET用来自动生成CustId新增行的占位符。

clip_image002

图2 数据表中CustId的属性

        数据库中属性AutoIncrement对应于Identity,属性AutoIncrementSeed和AutoIncrementStep 分别对应于Identity Seed和Identity Increment。你可能对AutoIncrementSeed和AutoIncrementStep都设置为-1感到奇怪。原因是这样可以保证ADO.NET生成的占位符的值不会和数据库中已有的值冲突,另外一个好处是它看起来不真实,所以用户会知道它仅仅是一个临时的占位符。

现在如果你点击MyCustomerTableAdapter header并且显示出Insert命令,你会看到:

INSERT INTO [MyCustomer] ([Name], [Company]) VALUES (@Name, @Company);

SELECT CustId, Name, Company FROM MyCustomer WHERE (CustId = SCOPE_IDENTITY())

这些命令行包括了两个语句,第二个语句在Insert操作提交后用来检索主键值。注意SCOPE_IDENTITY函数的运用,在MSDN中获取详细信息。

第二个插入语句的自动生成是由TableAdapter Configuration Wizard(表3)中的“refresh data table option”控制的。

clip_image003

3 TableAdapter Configuration Wizard中刷新数据表的高级选项

        现在让我们来体验一下在运行时应用refresh data table 功能。打开窗口,显示出Data Sources Window,将表MyCustomer拖到窗口上得到如下的布局(图4):

clip_image004

图4 设计时:将表MyCustomer从DataSourcesWindow拖到窗口上

按下F5运行,点击Add New(the+sign)按钮增加新的行,注意我会得到的CustId 列的值分别为-1,-2, -3等。

clip_image005

图5 CustForm运行时(提交之前)

现在我点击Save(the disk sign)按钮,CustId的值更新为9,10,11。哦!我本来期望的是2,3,4;

可能是有人在我操作之前增加了新的行。在这里你可以看到-1,-2,-3的优势,它们清楚的表明了没有提交的值。

clip_image006

图6 CustForm运行时(提交之后)

        现在如果你使用MS Access 数据库或者SQL CE,你将会看到以上的步骤不会像期望的那样工作。当你点击Save,主键-1,-2,-3是保持不变的。如果你检查TableAdapter Configuration Wizard, Refresh the data table 选项是不可用的。如果你检查生成的Insert命令,只有一条语句。原因是MS Access 数据库和SQL CE不支持SQL 批处理语句,因此不能使用SCOPE_IDENTITY()函数。Refresh the data table选项对于这些数据库是无效的。

        有一个好消息是找到一个解决办法,通过Adapter.RowUpdated事件重新设置主键标识值。请看Beth Massi的博客Using TableAdapters to Insert Related Data into an MS Access Database. Beth Massi 也会写一篇关于SQL CE的文章。

        总之,SQL server 或其它任何支持批处理的数据库通过使用SCOPE_IDENTITY()函数都可以可靠地检索标识值,Visual Studio Data Tool提供了自动生成Insert命令通过默认打开“Refresh the data table”选项。对于不支持批处理SQL 语句的数据库来说,通过RowUpdated事件重新设置主键值是好的解决方法。