使用 Visual Studio 宏将 ListView 或 FormView 模板格式化为表
默认的 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
代码在过程中调用了一些辅助函数,但它执行的基本步骤如下:
首先,代码获取选定的 FormView
或 ListView
文本,将其解析为 XElement
,并获取使用的模板列表。
接下来,它会弹出一个 Windows 窗体,允许用户取消选中他们不想更新的模板。
最后,它为每个选定的模板调用 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
,我们知道它是字段标签,我们创建一个新的 TableRow
和 TableCell
,将该节点添加到 TableCell
,然后将 TableRow
添加到已有的 Table
中。
如果节点是 XElement
并且 Name.LocalName
属性不包含“Button”,我们知道我们有一个数据源字段控件。所以我们创建一个新的 TableCell
,将控件节点添加到其中,然后将 TableCell
添加到已有的 TableRow
中。
如果节点是 XElement
并且它是 LinkButton
或 Button
,我们知道我们到达了模板的末尾,并且开始处理 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:修复了 的转义和反转义。
- 2011/6/15:添加了 ListView 作为支持的控件进行格式化。修复了模板选择窗体,使其显示在所有窗口之上,而不是显示在 Visual Studio 后面。