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

使用 Visual Studio 宏将 ListView 或 FormView 模板格式化为表

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2011 年 5 月 13 日

CPOL

6分钟阅读

viewsIcon

31997

downloadIcon

276

默认的 FormView 模板没有结构。使用此 Visual Studio 宏将模板格式化为表格。

引言

当你在 ASP.NET 中创建 FormView 或 ListView 并选择数据源时,你会得到自动生成的模板,这可以节省大量时间。FormView 模板非常基础,没有结构。ListView 模板默认显示为网格,但如果你将 ListView 配置为显示为“单行”,它具有与 FormView 相同的基本布局。字段由 <br /> 标签分隔,字段控件紧挨着标签,这使得表单看起来参差不齐且奇怪。

虽然格式化模板很容易,但如果你的数据源有很多字段,并且需要在多个模板和同一数据源的不同 FormView/ListView 之间重复此格式化,这可能会变得非常乏味和重复。

我的解决方案是创建一个 Visual Studio 宏,自动获取给定 FormView/ListView 的所有模板,并将字段标签和控件包装在基本的 asp:Table 布局中。

背景

Visual Studio 有“宏”,你可以在 IDE 中调用它们。宏使你能够编写 VB.NET 代码(不确定为什么不能使用 C#?)以编程方式与 IDE 交互。在此示例中,我与 IDE 中打开的窗口中用户选择的任何文本进行交互。我相信我的宏可以在 2005 及以上版本中使用,但我的代码使用了 .NET 3.5 中的 System.Linq 函数,因此请注意,你需要 .NET 3.5 才能使其正常工作。

DTE 是用于与 IDE 交互的类。在此示例中,我使用 DTE.ActiveDocument.Selection 来获取活动文档中当前选定的文本。DTE 是后期绑定的,实际上是“EnvDTE80.DTE2”。

有关宏的更多信息,请访问:http://msdn.microsoft.com/en-us/library/b4c73967%28v=vs.80%29.aspx

Using the Code

点击“视图”>“其他窗口”>“宏资源管理器”,或按 Alt+F8,可以查看宏资源管理器。右键单击“MyMacros”,选择“新建模块”,为其命名,然后单击“确定”创建模块。现在你应该会在“MyMacros”下看到该模块,双击它可以在 Visual Studio 宏 IDE 中打开你的空白模块。选择模块文件中的所有内容,并用我的代码替换它。

保存代码并关闭宏 IDE,回到 Visual Studio IDE。选择从 <FormView....><ListView....> 的开始标记到结束标记的所有标记。这些标记被解析为 XML,所以请记住,开始标记和结束标记必须完全匹配,大小写也要相同。在宏资源管理器中,右键单击 FormatView 子例程并选择“运行”。系统会提示你取消选择不想更新的模板,然后它会将所有模板转换为表格布局。

该模块有两个主要方法:需要运行的主要 FormatView 子例程,以及执行模板实际解析和格式化为表格布局的 ConvertTemplateToTableLayout 子例程。

这是 FormatView() 子例程

Sub FormatView()
    Try
        PopulateList()
        ' Make sure the user has selected some text
        Dim activeSelection As TextSelection = GetActiveText()
        If (String.IsNullOrEmpty(activeSelection.Text.Trim())) Then
            Throw New Exception("No markup is selected.")
        End If

        ' Create an XElement from the selected page markup
        Dim viewControl As XElement = GetSelectedTextAsXml(activeSelection.Text)

        ' Get the templates from the selected control
        Dim templateList As List(Of XElement) = GetTemplates(viewControl)

        ' Show a form to let the user choose which templates they want to replace
        Dim templateChooserForm As New SelectTemplatesForm(templateList)
        If (templateChooserForm.ShowDialog() = DialogResult.Cancel) Then
            Return
        Else
            templateList = templateChooserForm.TemplatesToLoad
        End If

        ' Convert each template to a table format
        For Each template As XElement In templateList
            ConvertTemplateToTableLayout(template, _
               viewControl.Elements()(0).Name.LocalName.ToLower())
        Next

        ' Convert the XDocument back into a string and overwrite selected text with new text
        Dim newText As String = GetNewText(viewControl)

        ' Create an undo context to undo the whole operation
        ' at once instead of having to undo each change individually
        If (DTE.UndoContext.IsOpen) Then DTE.UndoContext.Close()
        DTE.UndoContext.Open("FormatTemplates", False)

        ' Insert the new text
        activeSelection.Delete()
        activeSelection.Insert(newText, Nothing)

        ' Format the new text
        Try
            activeSelection.DTE.ActiveDocument.Activate()
            activeSelection.DTE.ExecuteCommand("Edit.FormatDocument")
            activeSelection.DTE.ExecuteCommand("Edit.ToggleOutliningExpansion")
        Catch ex As System.Runtime.InteropServices.COMException
            Debug.WriteLine(ex.GetType().ToString() & vbNewLine & vbNewLine & ex.Message)
        End Try
        'End If
    Catch ex As Exception
        Dim errorMessage As String = ex.Message + System.Environment.NewLine + _
            ex.StackTrace + System.Environment.NewLine
        If (ex.InnerException IsNot Nothing) Then
            errorMessage += System.Environment.NewLine + "Inner Exception:" + _
                            System.Environment.NewLine + ex.InnerException.ToString()
        End If
        Using newErrorMessageForm As New ErrorDialogForm(errorMessage, "Error")
            newErrorMessageForm.ShowDialog()
        End Using
    Finally
        If (DTE.UndoContext.IsOpen) Then DTE.UndoContext.Close()
    End Try
End Sub

代码在过程中调用了一些辅助函数,但它执行的基本步骤如下:

首先,代码获取选定的 FormViewListView 文本,将其解析为 XElement,并获取使用的模板列表。

接下来,它会弹出一个 Windows 窗体,允许用户取消选中他们不想更新的模板。

ScreenShot.jpg

最后,它为每个选定的模板调用 ConvertTemplateToTableLayout,将结果输出回 IDE,并格式化输出。

在输出结果之前,它会打开一个新的 UndoContext,使用 DTE.UndoContext.Open("FormatTemplates", False),因此如果你不喜欢结果,可以单击“撤销”,所有更改将一次性撤销,将你带回之前的代码。

将结果文本输出回 IDE 后,会调用两个方法来格式化结果:

activeSelection.DTE.ExecuteCommand("Edit.FormatDocument")
activeSelection.DTE.ExecuteCommand("Edit.ToggleOutliningExpansion")

Edit.FormatDocument 的功能与点击“编辑”>“高级”>“格式化文档”相同,它只会对文档进行制表符对齐,使其美观。Edit.ToggleOutliningExpansion 只是收起所有行和表,以便你可以轻松看到新模板。

ConvertTemplateToTableLayout 子例程将为每个选定的模板运行一次。代码如下:

Public Sub ConvertTemplateToTableLayout(ByRef template As XElement)
    ' Remove all of the empty lines
    template.Nodes().Where(Function(n) TypeOf n Is XText AndAlso _
             String.IsNullOrEmpty(DirectCast(n, XText).Value.Trim())).Remove()
    ' Remove all "br" elements since we're now going to be using a table layout
    template.Descendants().Where(Function(a) a.Name.LocalName = "br").Remove()

    Dim templateNodes As IEnumerable(Of XNode)
    If (templateType.ToLower() = "formview") Then
        templateNodes = template.Nodes().ToArray()
    ElseIf (templateType.ToLower() = "listview") Then
        ' skip the first <td> node
        templateNodes = DirectCast(template.FirstNode, XElement).Nodes()
    Else
        Return
    End If

    ' Create the asp: namespace and the table element to hold the new table rows and cells
    Dim asp As XNamespace = "http://System.Web.UI.WebControls"
    Dim tableElement As New XElement(asp + "Table", _
        New XAttribute("runat", "server"), New XAttribute("id", "TableFields"))
    Dim row As XElement = Nothing
    For Each node In templateNodes
        If (row IsNot Nothing AndAlso row.Attribute("id") IsNot Nothing _
                AndAlso row.Attribute("id").Value = "CommandRow") Then
            Dim commandCellElement As XElement = row.Elements()(0)
            commandCellElement.Add(node)
        ElseIf (TypeOf node Is XText) Then
            ' Dealing with the label. Add a new row
            ' to the table and add a cell with the label.
            Dim label As XText = DirectCast(node, XText)
            label.Value = label.Value.Trim()
            Dim rowID As String = template.Name.LocalName + _
                label.Value.Trim().TrimEnd(Char.Parse(":")).Replace(".", _
                "_").Replace(" ", "_") + "Row"
            Dim cellElement As New XElement(asp + "TableCell", _
                New XAttribute("CSSClass", "YOURCLASS"), label)
            row = New XElement(asp + "TableRow", New XAttribute("id", rowID), _
                      New XAttribute("CSSClass", "YOURCLASS"))
            row.Add(cellElement, System.Environment.NewLine)
            tableElement.Add(row)
        ElseIf (TypeOf node Is XElement) Then
            ' Dealing with the field. Add a new cell to the row with the field.
            Dim field As XElement = DirectCast(node, XElement)
            If (row IsNot Nothing And False = field.Name.LocalName.Contains("Button")) Then
                Dim cellElement As New XElement(asp + "TableCell", _
                    New XAttribute("CSSClass", "YOURCLASS"), field, System.Environment.NewLine)
                row.Add(cellElement, System.Environment.NewLine)
            Else
                ' Button's at the bottom of the template.
                Dim commandCellElement As New XElement(asp + "TableCell", _
                    New XAttribute("id", "CommandCell"), field, System.Environment.NewLine)
                row = New XElement(asp + "TableRow", New XAttribute("id", "CommandRow"))
                row.Add(commandCellElement, System.Environment.NewLine)
                tableElement.Add(row)
            End If
        End If
    Next

    template.Nodes.Remove()
    If (templateType.ToLower() = "formview") Then
        template.Add(tableElement)
    ElseIf (templateType.ToLower() = "listview") Then
        ' Add the outer td element back in
        Dim outerTD As New XElement("td", New XAttribute("runat", "server"), tableElement)
        template.Add(outerTD)
    End If
End Sub

首先,它会删除模板中的所有 <br /> 节点以及空白或仅包含空格的节点。然后它创建 asp XNamespace;我使用了“http://System.Web.UI.WebControls”,因为这是 asp 前缀的编程命名空间,但 XML 命名空间可以是任何你想要的。

然后它创建一个新的 asp:Table 并遍历模板中的所有 XML 节点。首先,我们检查是否在 CommandRow(在模板结束时创建)中,如果是,它只是将该点到结束的所有节点添加到 CommandCell

如果节点是 XText,我们知道它是字段标签,我们创建一个新的 TableRowTableCell,将该节点添加到 TableCell,然后将 TableRow 添加到已有的 Table 中。

如果节点是 XElement 并且 Name.LocalName 属性不包含“Button”,我们知道我们有一个数据源字段控件。所以我们创建一个新的 TableCell,将控件节点添加到其中,然后将 TableCell 添加到已有的 TableRow 中。

如果节点是 XElement 并且它是 LinkButtonButton,我们知道我们到达了模板的末尾,并且开始处理 Edit、Insert、Update 等按钮。

在处理完模板中的所有节点后,它们都会被删除,并被最终的 Table XElement 替换。

关注点

当我开始编写这个宏时,我记录了我修改每个模板所做的所有按键操作。宏使用了大量的 TextSelection 方法来选择文本并用标签替换它。它需要为每个模板运行一次,而且非常不稳定。我认为使用 XElement 来进行更改效果更好,但我无法弄清楚如何解析 asp 标签前缀。当我调用 XElement.Load 时,它说 asp 前缀未识别或类似的消息。最后,我只是将选定的文本用一个包含 xmlns 属性的外部 SelectedText 标签包装起来,这样一切都正常工作了。处理完成后,我只是再次删除了 SelectedText 标签。

历史

  • 2011/5/13:首次发布。
  • 2011/5/13:添加了在发送 Edit.FormatDocument 命令之前激活文档的行。
  • 2011/5/16:修复了 &nbsp; 的转义和反转义。
  • 2011/6/15:添加了 ListView 作为支持的控件进行格式化。修复了模板选择窗体,使其显示在所有窗口之上,而不是显示在 Visual Studio 后面。
© . All rights reserved.