[原文作者]:Amanda Silver
[原文链接]:Hidden Gems in Visual Basic 2008
昨天我答应写一些大家在以前的博客中没有看到的关于Visual Basic和Visual Studio 2008的新东西,我把它叫做隐藏宝石。我曾提到过在VS2008中的一些特性我很喜欢,还炫耀过智能感知(Intellisense )的改善,尤其是对关键字、本地变量和表达式,自动完成语句的改进。在过去,我还提到过一个新的语法特性,称之为Relaxed Delegates,它允许你为事件(event)提供一个可替代的签名(alternate signatures)。以上这些都是很好的特性,但我今天要说的不是这些,是隐藏宝石(可能这些你在一些C#的文章中见过了,在Visual Basic 2008中一样炫)。
我挑选出了10个最重要的特性,只是简单的介绍一下,有兴趣的可以继续深入研究。
首先,从一个基调特性(keynote feature)开始。
0)多个target Framework支持(Multi-targetting)
简单的说,Multi-targetting支持你在VS2008或VB9下用.NET 2.0 framework进行编译。下面我将要讲到的所有特性都在.NET 2.0下有效,所以当你打开Visual Studio创建一个project以后,把framework target设置为2.0(除了第5和第7项,因为它们需要LINQ到Objects和XML API而LINQ是在.NET 3.5才推出)。如图:

1) 输入推断(Type Inference)
在Visual Basic 9里面,下面的这一小段代码以及Nothing都是延迟绑定(late-bound)——在编译时(compile tine)一切都是延迟绑定,这就意味着你可以得到智能感知和输入推断。
Dim dialog = New OpenFileDialog()
Dim result = dialog.ShowDialog()
Dim printStr = "C:\"
If result = Windows.Forms.DialogResult.OK Then
printStr = dialog.FileName
End If
MsgBox(printStr)
这使得在输入代码时更加快捷,简单和准确。
2) IF操作符
还记得吗,IIF函数能返回一个对象(Object),这意味着你不需要通过智能感知或类型推断就可以得到默认的返回值(Object类型)。如果你坚持要类型安全或代码前绑定,可以强制转换,代码如下:
Dim intC As Integer = CInt(IIf(intA = intB, intA, intB - 1))
现在用IF操作符,你可以这么写:
Dim intD As Integer = If(intA = intB, intA, intB)
如果加上类型推断,代码就更简单了:

3) 对象初始化(Object Initializers)
总的来说,在.net framework里,对象初始化是一种类似于把Dim和statement combined整合在一起的表达式。这样使得参数构造器多少让人容易接受一些:
Dim strm As New StreamWriter( _
New FileStream("C:\out.txt",FileMode.OpenOrCreate) _
With {.Position = 10})
对象初始化使得创建一个数组对象更加容易:
Dim Capitals() As City = {New City With {.Name = "Antanarivo", .Country = "Madagascar"}, _
New City With {.Name = "Belmopan", .Country = "Belize"}, _
New City With {.Name = "Monaco", .Country = "Monaco"}, _
New City With {.Country = "Palau", .Name = "Koror"}}
4) Nullable
Nullable 是一种特性,可能你知道但没有关注过。它是一个基本的.NET 表达式,专门针对nullable类型,主要用于LINQ。Nullable支持你写一段代码,其中的属性以null值传播。例如下面这点代码,在Country type中有个independence property 是nullable date。
Dim virginIslands As New Country With {.Independence = Nothing}
Dim palau As New Country With {.Independence = #10/1/1994#}
Dim vILength = #8/24/2005# - virginIslands.Independence ' Nothing
Dim pLength = #8/24/2005# - palau.Independence ' 3980.00:00:00
5) LINQ to DataSet
它意味着你可以不用调用其他数据访问技术就能收获LINQ的好处。先填充一个DataSet,然后就可以对这个DataSet进行查询。
Me.EmployeesTableAdapter.Fill(Me.NORTHWNDDataSet.Employees)
Dim query = From emp In Me.NORTHWNDDataSet.Employees _
Where emp.Country = "USA" _
Order By emp.HireDate _
Select emp
Me.EmployeesBindingSource.DataSource = query
6) 语法提示(Syntax Tooltips)
看看这个是不是很cool?

再看看这个

还有这个

7) 当namespace被用在XML 文档中时,智能感知会对namespace前缀和local name进行匹配,你只要为输入带来很大便捷,你只要输入开头几个字母然后回车,VS会帮你找到匹配的字段并加上相应的前后缀。下面是个小例子,以一个输入文件开始,然后使用智能感知。

此时我们只输入字母t,VS会自动选中tomato。

8) GoTo Type Definition
通常,当你定义了一个变量,你想通过Object Browser浏览它在代码中的类型定义的时候,现在你多了一种选择,通过context menu可以让你直接找到它的定义。这点非常好,尤其是涉及到类型推断时能帮你确定该变量的类型是否和你想象的一致。

9) 循环变量的输入推断(Type inference for loop variables)
检查下面这段代码:

还有这段:

如果没有指定控制变量的类型,它会根据表达式或循环的信息从右往左推断。
10) 性能的提高以及非封闭性操作
后台编译器有一个非常强大的特性,它可以给你及时地反馈只要你写的代码正确。在这个版本的Visual Studio中我们对后台编译器做了很大的改进,我们相信后台编译器比以前快3倍但只用原先1/3的内存。任何使用过VS2008的人都会意识到这一点。虽然我们在性能上做了很大的改进,但是在大项目里面某些操作符仍然是一个巨大的花费,例如改变一个base class的declaration 通常会被多次用到。如果在后台编译器工作之前,你试图调用一些依赖于编译信息的特性(例如Intellisense或Drop Downs),在以前版本的VS中会有一个长时间的停顿直到编译完成为止,但是现在这个问题解决了,当你想得到drop downs的时候就像这样:

VS很快会有响应。
[原文作者]: Bill Horst
[原文链接]: Converting SQL to LINQ, Part 2: FROM and SELECT (Bill Horst)
在看这篇文章之前,我假定你已经读过了从SQL到LINQ,Part 1:基础。
为了让代码示例更清晰,我修改了下列名字:
· Customers -> CustomerTable
· Orders -> OrderTable
· cust -> Contact
· CustomerName -> ContactName
· ID -> ContactID
欢迎你们的任何反馈和建议,你们的意见可以使以后的文章更清晰更有用。
好,现在开始讨论具体的子句(clauses),我们将从最基础的FROM和SELECT开始。
FROM
SQL的SELECT语句由SELECT子句开始,并且紧跟着一个FROM子句。而VB查询表达式则由From子句(或者Aggregate子句,我们稍后讨论)开始。一个基本SQL的FROM子句指定了一个要操作的表,一个LINQ的From子句指定了一个我们要操作的对象(CustomerTable)。这个对象可以表示“已在内存中的”(“In-Memory”)数据:比如一个SQL表,或者XML信息。因为使用这样的数据比较简单,我的例子也采用了”In-Memory”的数据。除了这个数据对象,VB的From子句还包含了一个指定当前“行”(Contact)别名的标识符。
如果要选择所有的列,在SQL中我们需要使用”*”,而在VB中,我们不需要附加任何东西,From子句默认返回所有的内容。
|
SQL |
|
SELECT *
FROM CustomerTable
|
|
VB |
|
From Contact In CustomerTable
|
FROM里的别名(alias)
SQL允许你在FROM子句中指定一个表的别名,LINQ同样允许我们这样做。
|
SQL |
|
SELECT Contact.CustomerID, Contact.Phone
FROM CustomerTable Contact
|
|
VB |
|
From Contact In CustomerTable
Select Contact.CustomerID, Contact.Phone
|
SELECT
SQL的SELECT语句由一个包含要select的内容的列表开始(Name, CustomerID)。 类似的,LINQ也允许你指定要select的内容,并将结果包装成一个匿名类型(anonymous type)返回。你指定的内容并不一定要是From子句指定对象的一部分,你可以指定任意合法的VB表达式(比如3+4)。如果成员的名字不能够被推断,你必须为其指定一个别名(见下面的“SELECT里的别名”)。
|
SQL |
|
SELECT Name, CustomerID
FROM CustomerTable
|
|
VB |
|
From Contact In CustomerTable
Select Contact.Name, Contact.CustomerID
|
SELECT里的别名
SQL允许SELCT子句的成员有别名(ContactName, ContactID),我们可以在查询语句的其它子句中使用这些别名。LINQ也允许别名,并且你可以在所有使用这个查询结果的代码中使用它们。
|
SQL |
|
SELECT Name ContactName, CustomerID ContactID
FROM CustomerTable
|
|
VB |
|
From Contact In CustomerTable
Select ContactName = Contact.Name, ContactID = Contact.CustomerID
|
下次我打算讲到DISTINCT, WHERE, ORDER BY 和运算符。
- Bill Horst, VB IDE Test
[原文作者]:Kit George
[原文链接]:LINQ Cookbook, Recipe 2: Find all capitalized words in a phrase and sort by length (then alphabetically) (Kit George)
准备材料:
- Visual Studio 2008 (Beta2 或更高版本)
- 一些需要搜索的字符串
类别: LINQ-To-Objects, LINQ and string, LINQ and WinForms
制作方法:
- 打开Visual Studio 2008,点击菜单“文件/新建项目”,找到并双击 ”Windows 窗体应用程序” 图标
- 拖放一个Listbox 到窗体上,调整它的高度,然后拖放一个按钮到窗体上
- 双击这个按钮,并将下面的代码添加到按钮的事件处理函数中:
Dim text = "Good morning everyone. I'd like to welcome " & _
"you to today's presentation on LINQ. My " & _
"name is Kit George and I'm a Program Manager " & _
"for Microsoft, on the Visual Basic team. You " & _
"might be wondering where my accent is from? " & _
"Well, I hail from a small country called New " & _
"Zealand but it sure is great to be here in " & _
"Atlanta today!"
Dim capitalWords = _
From word In text.Split( _
New Char() {",", ".", "!", " "}, _
StringSplitOptions.RemoveEmptyEntries) _
Where word(0) = Char.ToUpper(word(0)) _
Order By word.Length, word
ListBox1.Items.AddRange(capitalWords.ToArray())
- 修改你要搜索的字符串。如果字符串保存在一个文件中, 可以使用 My.Computer.FileSystem.ReadAllText 获取并保存到字符串变量中。
[原文作者]: Beth Massi
[原文链接]: Visual Studio Tip of the Day - Let's Build an Add-In
我已经开始使用Visual Studio 创建一些Add-In的工程, 这的确是一个比较容易的过程. 真正值得高兴的是,我们不需要去单独安装SDK 去创建Add-in工程,这已经集成在Visual Studio中. 我之前写过一个从Sara的rss 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程序的确很简单.你也可以一些尝试其他的例子,比如Office的Add-in等等.
在以后的文章中,我会写一些关于如何把Add-in程序部属到其他机器的方法.
[原文作者]: Bill Horst
[原文链接]: Converting SQL to LINQ, Part 1: The Basics
你可能已经知道了,VB的LINQ指令允许使用类SQL语法的查询。LINQ的语法并不和SQL的语法完全一致,所以如果你现在已经在用SQL或者熟悉SQL查询的话,你可能会想把已经存在的SQL查询转化为LINQ。
这篇文章就是“从SQL到LINQ”系列的第一篇。在这篇文章里,我想让大家对SQL和LINQ的区别有一个基本的理解,下次我们会涉及到具体的语言架构。
假定
在接下来的例子, SQL代码会涉及到两个table:Customers和Orders,VB代码会用到与之同名的两个对象,他们的类型分别是IEnumerable(Of Customer)和IEnumerable(Of Order)。我还会用到两个类,同样是Customer和Order,如下:
|
Class Customer
Public CustomerID As Integer
Public Name As String
Public Phone As String
Public Address As String
Public City As String
Public State As String
Public Zip As String
End Class
Class Order
Public OrderID As Integer
Public CustomerID As Integer
Public Cost As Single
Public Phone As String
Public OrderDate As DateTime
Public ShippingZip As String
Public ItemName As String
End Class
|
基本语法
LINQ支持SQL SELECT语句,而不支持其他的,比如CREATE, INSERT, UPDATE 和 DELETE。你可以认为SQL SELECT语句的基本语法是一系列的“子句”(clauses),其中的第一个是SELECT子句。
|
sqlSelectClause [ sqlClause1 [ sqlClause2 [ ... ] ] ]
|
SQL的语法可能根据不同的版本有所差异,下面是一个例子:
|
SQL |
|
SELECT Name CustomerName, CustomerID ID
FROM Customers
ORDER BY ID
|
VB LINQ表达式的基本语法也是一系列的子句,不同的是第一个是From子句(或者Aggregate子句,我们稍后会讨论)。
|
linqFromClause [ linqClause1 [ linqClause2 [ ... ] ] ]
|
比如:
|
VB |
|
From cust In Customers _
Select CustomerName = cust.Name, ID = cust.CustomerID _
Order By ID
|
我刚才用了“VB LINQ表达式”的说法是因为前面的“LINQ查询”不是一个完整的语句。虽然SQL语句可以单独出现,但是LINQ查询在语法上和表达式“3 * 4”一样 - 它不是一个完整的语句,所以我们要使它完整。一个VB的完整的LINQ查询的语句看起来像这样:
|
VB |
|
Dim SortedCustomers = From cust In Customers _
Select CustomerName = cust.Name, ID = cust.CustomerID _
Order By ID
|
从概念上说,LINQ查询的每个子句操作的都是IEnumerable(Of T)类型并且返回的也是IEnumerable(Of T)类型”(其中的两个T并不一定要相同)。查询子句和SQL子句是基本类似的(比如SELECT, ORDER BY),所以你可以一个子句一个子句地将SQL查询转化成LINQ。在上面的例子里,这些子句出现的顺序稍有不同,而且有些语法上的差异,但是你可以看到,他们非常类似。
还要注意的一点是,SQL语法允许在子句间直接换行,而在VB里,换行需要在每行最后加上下划线(_)去告诉编译器下一行仍然是当前的表达式。
这篇文章只是一个泛泛的说明,但我会在以后讲到更为具体的内容。我打算涉及以下条目:
- FROM and SELECT
- DISTINCT, WHERE, ORDER BY, Operators
- Functions (Scalar and Aggregate)
- GROUP By and HAVING
- Joins
- UNION, TOP and Subqueries
- Bill Horst, VB IDE Test
[原文作者]:Beth Massi
[原文链接]:LINQ to SQL and One-To-Many Relationships
最近,有人问我关于发表在creating a one-to-many form with LINQ to SQL 上的一个视频的一个问题。在这个视频示例中,我用Northwind数据库,在Cusomers和Orders两个实体间建立了一对多的关联。我们总是喜欢用Northwind做示例,但是有时候我自己都忘记了它的设计有多么不切实际,例如随处可见的nullable foreign keys,child table的外健值(例如Orders表中的CusomerID或者Product 表中的CategoryID)被设置为nullable也就表示这些child rows在parent table中可以没有对应的row存在。
通常情况下,对于主外健关联,外健值是设置为non-nullable,就是为了child rows 与parent table中的row相关联的,这也是为了保证数据库中数据的完整性。外健值为nullable这种情况一般用于查询,这时查询的关键字可以为空。外健值设置为non-nullable,也表示在删除child table中child rows前,首先要先删除parent table中所对应的row(除非你明确设置主外健关联的delete rule 为"Cascade")。下图是Customers和Orders的典型实例:两个表建立主外健关联,外健值设置为non-nullable ,
当我们应用O/R Designer和LINQ 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和表Orders从Server Explorer拖拽到O\R designer界面后,实体Customer和Order及它们之间的关联就会自动生成:

参考一下dbml的xml文件(在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 class的DeleteCustomer 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 如何实现这些步骤我们就不用关心了.
[原文作者]:Beth Massi
[原文链接]:An Example of Dynamic Programming in VB
在论坛里已经有很多人问我要VB动态编程的实例。大家都已经了解使用动态编程的优势,但对于如何在VB中使用的一些细节仍不太清楚。由于动态编程这个词是在最近才开始流行的,现在仍有很多读者不熟悉这个术语,对于这些读者,我想首先要简单介绍一下什么是“动态”。
其实我的背景也是来自于一种类似Visual Basic的动态编程语言,但是相比于VB,他有更好的数据处理和综合查询能力,并且从1995年后,他也更倾向于面向对象编程。这个语言就是Visual FoxPro。VFP和VB都具有的一些优势包括他们都很容易对对象进行操作,并且在设计时并不要求完全明确数据的类型,也就是所谓的弱类型的语言。VFP所特有的优势是能够动态执行代码,较强的交互性和支持游标,对矩阵数据源的综合查询能力。它也丰富了基于元数据的编程风格,使它能够在运行过程中改变应用程序的行为而不需要重编译。VFP 的缺点是它是完全的动态,而没有任何的静态输入检查。大家都知道你要是不仔细的话,它会给你带来很大的麻烦。。
现在VB 9既有集成查询(Linq,并且它比VFP更完善)又有静态类型检查,所以我会选择VB。VB支持动态类型,当需要的时候,也支持动态类型。这是我所知的唯一一种兼具静态和动态类型能力的语言。对于那些我曾经专注于开发的基于数据和信息系统的应用程序来说,这是一个巨大的胜利。我们需要能够简单的对这类应用程序进行设置和个性化而不需要重编译,同样我们也需要一种语言能够让我们在简单的做这些操作的同时也能够减少写代码的工作。
Visual Basic动态编程的构建和交互性仍有很大的拓展空间,并且我认为在VB 10里我们就能够看到这些改进,不过,我还是想向你展示一下使用VB 8 (VS 2005)和VB 9 (VS 2008)来做动态编程的优势。为了证实这点,我会创建一个简单的动态UI界面,他会通过读取一个XML文件来在Windows Form界面上显示一些控件。我们将用VB 8来做这个工作,同时我会在使用VB 9和VS 2008开发程序的时候,演示一些XLinq的使用。
一个VB 8.0, VS 2005动态编程实例
我已经创建了一个名为questions.xml的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>
<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>
然后我就有了一个名为Form1的Windows Form。在这个form的滚动区域,我想显示一些固定的内容和一些动态的内容。为了实现这个,我添加了一个Panel,并且让他可以滚动,然后在panel的内部加了一个TableLayoutControl。我把这个控件设置为固定有2列(column),即提问和回答,但是他会动态的增加行数(rows) ,行数是基于XML 文件中问题的数量。为了能够兼容早于VB 8的XML版本,我会把他载入一个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 ON和Option 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 8和Visual Studio 2005中的操作。
同样的例子,使用VB 9.0,VS 2008,和指向XML的LINQ连接
现在我们想把这个程序升级到VB 9和VS 2008。为什么要升级?为了展现XML的LINQ连接的优势,和它所带来的智能感知。这样我们就可以提升我们程序的安全性。在你编码的时候,很重要的一点就是,能够注意到动态编码和静态编码的平衡性。
当我们用VB 9来创建一个工程时,默认的情况下,Option Strict是设置为Off的。不过,这里有一个新的设置,名为Option Infer,并且它的设置是On。这就意味着我们不需要很明确的指定本地变量的类型,因为编译器会对变量进行类型推断。当编译器对变量进行类型推断的时候,它们实际是强类型的。类型推断能够更好的提供对动态编程的支持。
首先我们需要做的是引用一些命名空间,包括:System.Linq、System.Xml.Linq和XML的命名空间。接着,通过在VS中打开XML文件并且在XML文件的菜单栏中选择“创建Schema"来为 questions.xml 文件创建了一个XSD schema。然后我把这个schema保存在工程中。这样,我们就能够引用这个命名空间了:
Imports <xmlns:ns="http://www.w3.org/2001/XMLSchema">
引用了上面namespace后,在我们处理包括“question”的XElement的时候,在VS IDE里就有了智能感知。在动态编程过程中,智能感知能给我们很大的帮助。这样,我们就不需要将XML导入到DataSet中,而是可以创建一个基于XML的LINQ查询(也就是XLinq),这个查询可以返回给我们一组Xelement对象的集合。然后我们来修改动态类使其能够处理这些元素而不是通常的对象类型。首先读入XML 文件:
Dim survey = XElement.Load(CurDir() & "\questions.xml")
注意,这里的survey变量并不是一个object对象,编译器由它右边的赋值表达式推断出是一个Xelement类型。下面我们使用...语法来选出所有的question的Xelement,不管它在文件中的路径有多深。因为我们已经引用了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查询非常简单,并且只要我们在这个查询代码中加入适当的Where或Order By,就能够很轻松的选出某一些我们需要的问题,或者按照我们的要求排序。既简单又文雅。
有很多方法可以创建静态的Question。在这儿,为了演示镶嵌的表达式,我们用Linq与XLinq整合的方式来做。首先创建了一个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 2005和VS 2008的工程文件。
Enjoy!
更新:点击这里,关于动态类的延伸。
[原文作者]: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 文本” 再定置颜色到RGB(163,21,21)。这里是另外一个例子,一些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文件,你可以用它来生产任何基于文本格式的文件。
[原文作者]: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 命名空间。这样你可以在一个项目中处理多个命名空间。
好了,到这里我希望已经给了你们一个关于VB中XML的很好的介绍。关于这个新语言特性,真的有太多的事情可以做了,所以并没能将他们一一的列写出来。期待着推出更多的教程视频
Enjoy,
-B
[原文作者]: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)。这个可以用VB8的DataSet轻松做到:
'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对象一起传入Dynamic的GetQuestion方法,这里的info对象既可以是一个DataRow也可以是我们之前创建的静态的QuestionInfo对象。注意我并没有在前面更改过QuestionInfo类,下面这段代码会忽略任何在info对象中无法找到的属性或者无法通过CallByName赋值给c(c就是在下面代码中我们动态创建的控件)的属性。这就可以使代码更加动态化,因为现在,我们并不必要知道我们创建对象的类型,也不必知道需要被设置的属性:
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)
'VB does an automatic conversion at runtime
For Each prop As String In properties
Try
'Late bound call - we don't know the type or the value
propValue = info(prop)
Catch ex As Exception
propValue = Nothing
End Try
If propValue IsNot Nothing AndAlso propValue IsNot System.DBNull.Value Then
Try
CallByName(c, prop, CallType.Set, propValue)
Catch ex As Exception
'if we can't set a property on the object, just ignore
End Try
End If
Next
Catch ex As Exception
'Try/Catch is required here, as this code will cause
' a runtime error if the 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仍有很长的一段路要走,但是今天我们已经能够开始使用Visual Basic来实现动态编程。
Enjoy!
Attachment(s):DynamicUI.zip