65.9K
CodeProject 正在变化。 阅读更多。
Home

创建用户友好的 Web 数据采集表单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (15投票s)

2012 年 10 月 29 日

CPOL

6分钟阅读

viewsIcon

50737

downloadIcon

851

一个电子数据捕获解决方案,允许授权的临床研究人员轻松地在线设计、创建、编辑和填写 CRF,而无需任何编程经验。这是一个 VB.Net Web 应用程序,使用 SQL 或 MS Access 后端的 EAV 表。

引言

作为临床研究团队的一员,我们认识到准确数据收集的必然需求。通常,带有结构化字段的标准表单在临床研究方案的病例报告表(CRF)部分进行描述。  

CRF 的完整性和准确性通过一组下拉菜单和编码到用于提交收集数据的软件中的验证脚本来维护。通常,临床研究电子数据捕获软件要么由机构的 IT 部门“内部”构建,要么从商业供应商购买,要么从开源临床研究软件联盟采用。所使用的软件要么实现“经典”的规范化数据库表,要么实现实体-属性-值(EAV)模型。

虽然采用现成的开源电子数据捕获软件可能看起来很诱人,但由于选择受限于特定的平台、数据库或 Web 服务器,可能会出现许多问题。软件的定制以完全满足您团队的需求也将受到限制,并依赖于社区或联盟的支持。此外,一些联盟会设置可能不适合临床研究团队的条件和法律限制。购买的软件也存在同样的问题,而且您还需要为此付费。

我们在临床研究方面的经验表明,需要一个电子数据捕获软件,该软件允许授权的临床研究人员轻松地在线设计、创建、编辑和填写 CRF,而无需任何编程经验。结果 CRF 的字段应包含(根据设计者的偏好)经过 Regex 验证的文本框和下拉列表,但还应显示由设计者选择的自定义消息的工具提示。为了提高准确性,应为某些字段设置“跳过逻辑”。因此,我们决定使用 VB.NET 构建我们自己的电子数据捕获 Web 应用程序,以满足前面描述的需求。

本示例中使用的数据库是 MS Access。但是,使用 SQL Server 作为后端只需要对代码和连接字符串进行少量修改。

数据库由 3 个表组成:maintab、FollowUp 和 CRF。

“maintab”包含患者姓名、病历号(或研究号)以及关于患者的一些“自由文本注释”。此表有两个目的:首先,强制的引用完整性将防止提交错误或缺失 MRN 的数据。此表还充当“现场”验证,因为它在表单提交后立即告诉用户他提交的数据是否与正确的患者姓名和 MRN 匹配。

 

“FollowUp”表将包含已提交表单的数据,其中字段“FU”代表病例报告表单的项目,例如姓名、年龄、性别、诊断等。字段“FUData”将包含该特定项目的数值,例如“13 years”对应于项目“Age”,或“female”对应于项目“Gender”。 

 

“CRF”表将包含应用程序用于构建数据录入表单的数据。用户需要填写的项目将进入“Item”字段。  

 

应用程序有两种模式:“设计模式”和“数据提交”模式。设计模式允许用户通过 Web 界面创建和编辑数据收集表单。这通过“add”和“update”查询写入“CRF”表来完成。

控件的“type”是“text”(文本框)或“DDL”(下拉列表)。该应用程序使用“Item”和“Type”中的数据动态创建文本框和下拉列表,对象名为“Item”。“options”字段将用列表项和相应的对应值填充,并用一个符号分隔,以便代码识别列表项与值的分隔位置,在本例中我们使用了“$”。 

对于文本框,字段“validation”是程序化创建的客户端验证控件使用的正则表达式的来源,其属性“ControlToValidate”从“Item”字段获取数据。 控件的其他属性,例如工具提示或依赖的跳过逻辑,也将通过编程方式添加到相关对象中。 

  

大多数临床方案都会告诉设计者表单字段名称、控件类型、字段是否必需以及每个字段的验证条件的确切规范。设计者只需将方案的建议“翻译”成要填写到 CRF 设计 Web 界面的值,无需编程或数据库设计。  

在数据提交模式下,用户将从下拉菜单中选择所需的 CRF。应用程序会将选定的 CRF 名称存储在会话中,并将用户重定向到数据录入页面。存储的会话将用于 select 查询,从“CRF”表中提取构建表单所需的数据。

 

加载的表单将显示从“CRF”数据库表调用的每个单独字段的属性。必需字段验证器和动态创建的正则表达式验证控件将保持数据的准确性和完整性,同时工具提示会在指定的字段弹出,以指导用户如何提交正确的数据。 

使用代码

该解决方案由两部分组成:第一部分负责数据的显示、搜索、导出到 Excel 和审计跟踪。为了节省时间,我们实现了一个经过许可的商业 ASP.Net 代码生成器来为上述功能创建 aspx 页面。第二部分,我们将在接下来的几行中讨论,是 CRF 创建和将提交的数据插入数据库的逻辑。

该解决方案的 Default 页面包含以下组件:“Label1”、“InsertData”按钮、“PlaceHolder1”和“CRFds”数据源。

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<div>
<asp:Label ID="Label1" runat="server" style="font-weight: 700; color: #516B92; font-size: large"></asp:Label>
<asp:Button ID="InsertData" runat="server" Text="InsertData" Width="85px"
style="height: 26px" />
</div>
<asp:PlaceHolder ID="PlaceHolder1" runat="server">
</asp:PlaceHolder>
<asp:AccessDataSource ID="CRFds" runat="server"
SelectCommand="SELECT [Item], [type], [options], [ID], [validation], [Tooltip], [required],[IndepControl],[IfValueEquals],[IsVisible] FROM [CRF] WHERE ([formname] = @formname)" 
DataFile="~/App_Data/DataBaseName.mdb">
<SelectParameters>
<asp:ControlParameter ControlID="Label1" Name="formname" PropertyName="Text" Type="String" />
</SelectParameters>
</asp:AccessDataSource>
</asp:Content>

“Page Load Event”将触发 Sub “Initialize CRF”

Private Sub InitializeCRF()
    Dim CRFgrid As New GridView
    Session("itemcount") = CRFgrid.Rows.Count
    buildCRF(CRFgrid, CRFds, PlaceHolder1)
End Sub    

然后将以下变量传递给函数“buildCRF”以构建带有相关跳过逻辑和客户端验证控件的表单。

Public Shared Sub buildCRF(ByVal CRFgrid As GridView, _
        ByVal CRFds As DataSourceControl, ByVal placeholder1 As PlaceHolder)

    CRFgrid.DataSource = CRFds
    CRFgrid.DataBind()

    Dim ItemCount As Integer = CRFgrid.Rows.Count


    'build data table using asp table

    Dim table1 As New Table
    table1.ID = "table1"


    For I = 0 To (ItemCount - 1)

        'build table row

        Dim myRow As New TableRow

        Dim itemname As String = CRFgrid.Rows(0 + I).Cells(0).Text.ToString

        Dim ItemNamelabel As New Label
        ItemNamelabel.Text = "" & itemname
        ItemNamelabel.Font.Bold = True

        'build cells inside each row
        Dim c1 As New TableCell()
        Dim c2 As New TableCell()
        c1.VerticalAlign = VerticalAlign.Top
        c2.VerticalAlign = VerticalAlign.Bottom



        Select Case (CRFgrid.Rows(0 + I).Cells(1).Text.ToString).ToLower
            Case "text"
                Dim ItemNameBox As New TextBox
                ItemNameBox.ID = itemname
                'This code is here to initialize skip logic

                If CRFgrid.Rows(0 + I).Cells(7).Text <> " " Then
                    'These nested loop will search for the independentent variable and make its postback = true
                    For Each Rw As TableRow In table1.Rows
                        For Each CL As TableCell In Rw.Cells
                            Dim IndepControl As New Control
                            For Each c As Control In CL.Controls
                                'If the independent control is found, enable its autopostback, and add attributes to the cell
                                If LCase(c.ID) = LCase(CRFgrid.Rows(0 + I).Cells(7).Text.ToString) Then
                                    'The previous IF condition is similiar to Findcontrol method
                                    IndepControl = c
                                    If TypeOf (IndepControl) Is TextBox Then
                                        CType(IndepControl, TextBox).AutoPostBack = True
                                    ElseIf TypeOf (IndepControl) Is DropDownList Then
                                        CType(IndepControl, DropDownList).AutoPostBack = True
                                    End If

                                    'depvar = independant control (gender); depval = condition (is female ); depresult = true visible or not
                                    c2.Attributes.Add("DepVar", CRFgrid.Rows(0 + I).Cells(7).Text.ToString)
                                    c2.Attributes.Add("DepVal", CRFgrid.Rows(0 + I).Cells(8).Text.ToString)
                                    c2.Attributes.Add("DepResult", CRFgrid.Rows(0 + I).Cells(9).Text.ToString)
                                End If
                            Next
                        Next
                    Next
                End If

                'End of skip logic initialization

                c1.Controls.Add(ItemNamelabel)
                c2.Controls.Add(ItemNameBox)

                If itemname.ToString.Equals("MRN") Then
                    ItemNamelabel.ForeColor = Drawing.Color.Red
                    ItemNameBox.BackColor = Drawing.Color.Yellow

                    Dim myrequired As New RequiredFieldValidator
                    myrequired.ControlToValidate = ItemNameBox.ID
                    myrequired.ForeColor = Drawing.Color.Red
                    myrequired.ErrorMessage = "required !"
                    c2.Controls.Add(myrequired)

                End If
                If CRFgrid.Rows(0 + I).Cells(6).Text.ToString = "1" Then


                    ItemNamelabel.ForeColor = Drawing.Color.Red
                    ItemNameBox.BackColor = Drawing.Color.Yellow

                    Dim myrequired As New RequiredFieldValidator
                    myrequired.ControlToValidate = ItemNameBox.ID
                    myrequired.ForeColor = Drawing.Color.Red
                    myrequired.ErrorMessage = "required !"
                    c2.Controls.Add(myrequired)

                End If
                Dim tmpv = CRFgrid.Rows(0 + I).Cells(4).Text.ToString

                Dim tooltipme = CRFgrid.Rows(0 + I).Cells(5).Text.ToString

                If tooltipme <> "" And tooltipme <> " " Then
                    ItemNameBox.ToolTip = tooltipme.ToString
                End If

                If tmpv <> "" And tmpv <> " " Then
                    Dim myValidator As New RegularExpressionValidator
                    myValidator.ValidationExpression = tmpv
                    myValidator.ControlToValidate = ItemNameBox.ID
                    myValidator.ErrorMessage = "wrong data type !"
                    c2.Controls.Add(myValidator)
                End If

                myRow.Cells.Add(c1)
                myRow.Cells.Add(c2)
                table1.Rows.Add(myRow)

            Case "ddl"

                Dim ItemNameBox As New DropDownList
                ItemNameBox.ID = itemname

                Dim tooltipme = CRFgrid.Rows(0 + I).Cells(5).Text.ToString

                If tooltipme <> "" And tooltipme <> " " Then
                    ItemNameBox.ToolTip = tooltipme.ToString
                End If
                Dim opts() As String = Split(CRFgrid.Rows(0 + I).Cells(2).Text.ToString, "$")
                Dim optsLabels() As String = Split(opts(0), ",")
                Dim optsVals() As String = Split(opts(1), ",")
                For t = 0 To UBound(optsLabels)
                    ItemNameBox.Items.Add(optsLabels(t))
                    ItemNameBox.Items.Item(ItemNameBox.Items.Count - 1).Value = optsVals(t)

                Next

                'This code is here to initialize skip logic

                If CRFgrid.Rows(0 + I).Cells(7).Text <> " " Then
                    'These nested loop will search for the independentent variable and make its postback = true
                    For Each Rw As TableRow In table1.Rows
                        For Each CL As TableCell In Rw.Cells
                            Dim IndepControl As New Control
                            For Each c As Control In CL.Controls
                                'If the independent control is found, enable its autopostback, and add attributes to the cell
                                If LCase(c.ID) = LCase(CRFgrid.Rows(0 + I).Cells(7).Text.ToString) Then
                                    'The previous IF condition is similiar to Findcontrol method
                                    IndepControl = c
                                    If TypeOf (IndepControl) Is TextBox Then
                                        CType(IndepControl, TextBox).AutoPostBack = True
                                    ElseIf TypeOf (IndepControl) Is DropDownList Then
                                        CType(IndepControl, DropDownList).AutoPostBack = True
                                    End If

                                    'depvar = independant control (e.g. gender); depval = condition (e.g. is female ); depresult = true visible or not
                                    c2.Attributes.Add("DepVar", CRFgrid.Rows(0 + I).Cells(7).Text.ToString)
                                    c2.Attributes.Add("DepVal", CRFgrid.Rows(0 + I).Cells(8).Text.ToString)
                                    c2.Attributes.Add("DepResult", CRFgrid.Rows(0 + I).Cells(9).Text.ToString)
                                End If
                            Next
                        Next
                    Next
                End If

                'End of skip logic initialization
                
                c1.Controls.Add(ItemNamelabel) : myRow.Cells.Add(c1)
                c2.Controls.Add(ItemNameBox) : myRow.Cells.Add(c2)
                table1.Rows.Add(myRow)
        End Select
    Next
    placeholder1.Controls.Add(table1)
End Sub 

 “InsertData”按钮单击将传递以下变量并触发“insertCRFdata”函数。请注意,“MRN”是病历号,是临床试验中患者的唯一标识符。

	Public Shared Sub insertCRFdata(ByVal mydb As OleDbConnection, _
          ByVal myitemcount As Integer, ByVal placeholder1 As PlaceHolder, _
          ByVal label1 As Label, ByVal activeuser As String)
    mydb = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data " & _ 
       "Source= |datadirectory|DataBaseName.mdb;Persist Security Info=True")
    mydb.Open()
    Dim part2 As Integer = myitemcount
    Dim myMRN As String = ""
    Dim myTable As New Table
    For Each T As Control In placeholder1.Controls
        If T.ID = "table1" Then myTable = CType(T, Table)
    Next
    For Each Rw As TableRow In myTable.Rows
        For Each CL As TableCell In Rw.Cells
            For Each C As Control In CL.Controls
                If C.ID =  "MRN" Then myMRN = CType(C, TextBox).Text
            Next
            For Each C As Control In CL.Controls
                If TypeOf (C) Is TextBox Or TypeOf (C) Is DropDownList Then
                    If C.ID <> "MRN" Then                                                                                  
Dim sqlstring = "insert into [followup] ([mrn], [fu], [fudata], " & _ 
               "[formname], [enteredby]) values (@mrntxt, @mylabel, @mydata, @myformname, @myenteredby);"
                    Dim mydbcommand As New OleDbCommand(sqlstring, mydb)
                    mydbcommand.Parameters.Add("@mrntxt", OleDbType.VarChar).Value = myMRN
                    mydbcommand.Parameters.Add("@mylabel", OleDbType.VarChar).Value = C.ID
                    If TypeOf (C) Is TextBox Then
                        mydbcommand.Parameters.Add("@mydata", OleDbType.VarChar).Value = CType(C, TextBox).Text
                    ElseIf TypeOf (C) Is DropDownList Then
                        mydbcommand.Parameters.Add("@mydata", OleDbType.VarChar).Value = CType(C, DropDownList).SelectedItem.Text
                    End If
                    '-------------
                    mydbcommand.Parameters.Add("@myformname", OleDbType.VarChar).Value = label1.Text
                    mydbcommand.Parameters.Add("@myenteredby", OleDbType.VarChar).Value = activeuser
                    '------------
                    mydbcommand.ExecuteNonQuery()
                    End If
                    End If
            Next
        Next
    Next
    mydb.Close()
    
End Sub

由于 EAV 表中存储的数据“按原样”不可用,因此我们需要找到一种方法将数据处理成有意义的表,其中包含“经典”字段和记录,以便随后由统计软件处理。

使用应用程序中提供的“高级”搜索页面,用户通过 Web 启用的过滤器选择要从 EAV 表“Followup”中提取的数据范围。然后,用户将 EAV 数据表导出到 MS Excel 文件。使用以下 VBA 代码行,EAV 表被“透视”成一个“经典”表。

Sub PivotBtn_Click()
Dim srcworksheet, trgtworksheet As New Worksheet
Set srcworksheet = Sheets(1)
Set trgtworksheet = Sheets(2)
Dim CurrentTargetRow, CurrentTargetColumn As Integer
CurrentTargetRow = 1
CurrentTargetColumn = 1
For i = 2 To 1E+21
    currMRN = srcworksheet.Cells(i, 2).Value
    If currMRN = "" Then Exit For
    currField = srcworksheet.Cells(i, 8).Value & "_" & srcworksheet.Cells(i, 3).Value
    currFieldDat = srcworksheet.Cells(i, 4).Value
    UniqueID = Str(srcworksheet.Cells(i, 5).Value) & " " & Str(srcworksheet.Cells(i, 6).Value) & " Entered by: " & srcworksheet.Cells(i, 7)
    'Search for the current MRN in the target sheet. If found, record its row number.
    'If not, then the row number should be set to the maxrownumber + 1 (i.e. new row)
    
    For t = 2 To 1E+21
        If Val(currMRN) = Val(trgtworksheet.Cells(t, 1).Value) Then
            If trgtworksheet.Cells(t, 2).Value = UniqueID Then
                CurrentTargetRow = t
                Exit For
            End If
        End If
        If trgtworksheet.Cells(t, 1).Value = "" Then CurrentTargetRow = t: Exit For
    Next
    
    'Search for the current field in the field labels of the target sheet.
    'If found, just add the value, if not, then add this label and increase the LastTargColumn count
    
    For r = 3 To 1E+21
        If currField = trgtworksheet.Cells(1, r).Value Then
            CurrentTargetColumn = r
            Exit For
        End If
        If trgtworksheet.Cells(1, r).Value = "" Then CurrentTargetColumn = r: Exit For
    Next
    
    'Set the values
    trgtworksheet.Cells(CurrentTargetRow, 1).Value = currMRN
    trgtworksheet.Cells(CurrentTargetRow, 2).Value = UniqueID
    trgtworksheet.Cells(1, CurrentTargetColumn).Value = currField
    trgtworksheet.Cells(CurrentTargetRow, CurrentTargetColumn).Value = currFieldDat
    
Next
End Sub

对于大型病例报告表,我们启用了 Excel 文件的离线组合,其中包含 CRF 数据。然后,用户只需上传文件,然后一些代码行将执行插入查询。 

我们也启用了“反透视”经典表,以帮助用户将现有的患者数据导入解决方案数据库。用户应在 Excel 工作表中使用以下 VBA 代码将经典表转换为 EAV。

Sub UnPivotBtn_Click()
Sheets("Sheet2").Cells(1, 1).Value = "MRN"
Sheets("Sheet2").Cells(1, 2).Value = "FU"
Sheets("Sheet2").Cells(1, 3).Value = "FU Data"
MaxRows = 999999
MaxColumns = 9999
x = 2
For i = 2 To MaxRows
    
    d = Sheets("Sheet1").Cells(i, 1).Value
    If d = "" Then Exit For
    
    For t = 2 To MaxColumns
        s = Sheets("Sheet1").Cells(1, t).Value
        e = Sheets("Sheet1").Cells(i, t).Value
        If s = "" Then Exit For
        Sheets("Sheet2").Cells(x, 1).Value = d
        Sheets("Sheet2").Cells(x, 2).Value = s
        Sheets("Sheet2").Cells(x, 3).Value = e
        x = x + 1
    Next
Next
End Sub
© . All rights reserved.