数据绑定和数据网格视图(DataGridView)控件
数据网格视图控件,不像我们前面看到的控件,它可以显示多个列,但是,数据必须格式化,使数据网格知道要显示哪一列。有两种实现方法:一个是把数据网格视图绑定到数据表(DataTable),另一个是把网格到绑定对象列表,对象有许多属性,不同的属性就成为网格的列。
下面的例子是一种简单的解决方案,绑定到数据集(DataSet):
open System
open System.Collections.Generic
open System.Configuration
open System.Data
open System.Data.SqlClient
open System.Windows.Forms
// creates a connections then executes thegiven command on it
let createDataSet commandString =
//read the connection string
letconnectionSetting =
ConfigurationManager.ConnectionStrings.["MyConnection"]
//create a data adapter to fill the dataset
letadapter = new SqlDataAdapter(commandString, connectionSetting.ConnectionString)
//create a new data set and fill it
letds = new DataSet()
adapter.Fill(ds)|> ignore
ds
// create the data set that will be boundto the form
let dataSet = createDataSet "selecttop 10 * from Person.Contact"
// create a form containing a data bounddata grid view
let form =
lettemp = new Form()
letgrid = new DataGridView(Dock = DockStyle.Fill)
temp.Controls.Add(grid)
grid.DataSource<- dataSet.Tables.[0]
temp
// show the form
Application.Run(form)
运行前面的代码,可以看到如图 9-2 显示的结果。
图 9-2 绑定了数据的数据网格
若不使用 DataSet,还可以使用 F# 记录类型。这样,通常需要创建一个泛型函数(generic
function),通过反射(reflection)创建并发布强类型集合。下面的代码演示了这种泛型函数,把它包装在模块中,这样,就能更方便地把它用于其他代码,然后,用这个模块执行对数据库的查询:
module Strangelights.DataTools
open System
open System.Collections.Generic
open System.Configuration
open System.Data
open System.Data.SqlClient
open Microsoft.FSharp.Reflection
// a command that returns dynamicallycreated stongly typed collection
let execCommand<‘a> commandString :seq<‘a> =
//the opener that executes the command
letopener() =
//read the connection string
letconnectionSetting =
ConfigurationManager.ConnectionStrings.["MyConnection"]
//create the connection and open it
letconn = new SqlConnection(connectionSetting.ConnectionString)
conn.Open()
//excute the command, ensuring the read will close the connection
letcmd = conn.CreateCommand(CommandType = CommandType.Text,
CommandText = commandString)
cmd.ExecuteReader(CommandBehavior.CloseConnection)
// the generator, that generates anstrongly typed object for each row
let generator (reader : IDataReader) =
ifreader.Read() then
//get the type object and its properties
lett = typeof<‘a>
//get the values for the row from the reader
letvalues = Array.create reader.FieldCount (new obj())
reader.GetValues(values)|> ignore
letconvertVals x = match box x with | :? DBNull -> null | _ -> x
letvalues = Array.map convertVals values
//create the record and return it
Some(FSharpValue.MakeRecord(t, values) :?> ‘a)
else
None
// generate the sequence
Seq.generate
opener
generator
(funr -> r.Dispose())
例子代码的第一行使用了一个我们之前尚未用到过的方法,显式声明了函数的类型参数:
let execCommand<‘a> commandString :seq<‘a>
这样做,能够显式给定泛型参数‘a,这个类型参数然后用于创建类型对象,再对它做反射:
let t = typeof<‘a>
这个函数是用来处理 F# 记录类型,它的字段完全匹配查询结果的字段;如果不满足这个先决条件,代码就失败;然而,这个先决条件通常是在应用程序中以反射的形式使用的。
前面已经定义的泛型函数 execCommand,能够用于任何查询,匹配记录类型。下面的代码演示如何应用:
open System
open System.Windows.Forms
open Strangelights.DataTools
// a type that mirrors the type of rowbeing created
type Contact =
{ContactID: Nullable<int>;
NameStyle:Nullable<bool>;
Title:string;
FirstName:string;
MiddleName:string;
LastName:string;
Suffix:string;
EmailAddress:string;
EmailPromotion:Nullable<int>;
Phone:string;
PasswordHash:string;
PasswordSalt:string;
AdditionalContactInfo:string;
rowguid:Nullable<Guid>;
ModifiedDate:Nullable<DateTime> }
// a form containing a data bound data grid
let form =
lettemp = new Form()
letgrid = new DataGridView(Dock = DockStyle.Fill)
temp.Controls.Add(grid)
letcontacts =
execCommand<Contact>"select top 10 * from Person.Contact"
letcontactsArray = contacts |> Seq.to_array
grid.DataSource<- contactsArray
temp
// show the form
Application.Run(form)
最重要的是下面一行:
let contacts =
execCommand<Contact>"select top 10 * from Person.Contact"
为泛型函数 execCommand 显式声明了类型参数。这个例子的结果同前面的例子,如图 9-2 所示。
注意
使用对象-关系映射,比例NHibernate,执行这种任务已经相当普遍,这些工具提供了高度的灵活性,但它们往往过度依赖于纯正 C# 的功能,要想在 F# 中使用,体验并不良好,因此,我在书中没有讨论任何相关内容。但是,许多人,包括我自己正在努力解决这个问题;同时,建议你关注我的博客:http://strangelights.com/blog。