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

保存和加载 ListView 控件的全部内容

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (8投票s)

2012年2月23日

CPOL

8分钟阅读

viewsIcon

34813

downloadIcon

1406

有时您需要将 ListView 的内容保存到用户的本地文件系统中。使用 .Net Framework 2.0 中的 XmlTextWriter 和 Reader 可以轻松管理这一点。


引言

如果您想保存 ListView 控件的内容,有许多可能性。您可以序列化所有项目,您可以遍历所有项目并将它们保存到 .ini 文件,或者您决定编写一个 XML (可扩展标记语言) 文件

在我看来,最后一个选项是最好的。您可以控制所有操作,因此该功能 `非常灵活且可修改`。

背景

我曾经需要保存 ListView 中的标题、组以及它们包含的所有项目。直到那时,我一直使用 `序列化` 函数,因为它使用简单。但这只保存了项目,而没有包含项目的组。所以我编写了自己的方法来保存和加载 ListView 控件的 `整个` 内容。

代码背后的逻辑

好了,我们决定编写一个 `XML 配置文件`。
这很简单,我们将遍历所有标题、组及其项目。

这意味着:
有几个 `XML 节点`

  • 文档以 `<ListView>` 开始
  • 然后一个名为 `<Columns>` 的节点为每个 ColumnHeader 包含一个“Single-Line-Node”;它们保存其最重要的数据(文本、宽度和文本对齐方式)。
  • 标签 `<Groups>` 包含每个组的一个节点
  • 在包含名称和标题对齐方式的 `<Group>` 标签内,每个项目都有另一个节点来包含其子项。

好吧 但那到底是什么意思
当您这样保存 ListView 时…

sample_lsv.png

… 输出配置文件如下所示

xml_scheme.png

带组的 ListView 与不带组的 ListView 不同!
现在,知道了我的函数如何工作,您必须区分使用组的 ListView 和不使用组的 ListView。

为什么?

也许您已经注意到该函数首先遍历每个组,然后遍历其项目。但是,如果 ListView 没有组会怎样?因为没有组可以搜索其项目,所以找不到任何内容。因此,配置文件 `将为空`。

因此,我们必须编写 `四个不同的工作函数`(两个用于保存;两个用于加载)。它们的区别在于,不带组的 ListView 的函数直接处理项目并跳过组,因为它们不存在。

最后,我们来谈谈代码。

ListViewContent 类

现在您知道了函数背后的逻辑,以及使用组的 ListView 和不使用组的 ListView 之间存在显著差异。因此,我们可以考虑文件是如何编写的。

我们可以使用 `System.Xml` 命名空间中的两个类

  • XmlDocument
  • XmlTextWriter

我选择了 `XmlTextWriter`,因为它比 XmlDocument 稍微容易处理一些。

让我们开始编写保存带组的 ListView 内容的函数。

ListViewWithGroupsSave

代码已经注释了,但我想补充一点。这并不难理解。该函数使用 `System.Xml` 命名空间中的 `XmlTextWriter`。然后它只是遍历我们想要保存的所有对象(标题、组、项目等)。

参数

  • lsv:这是将要保存的 ListView 控件
  • destinationPath:您要将配置文件保存到的路径。
    重要提示:您必须指定一个文件扩展名,例如 .xml、.txt、.cfg 或您想要的任何内容。

返回值
如果没有发生错误,该函数将返回一个空字符串,否则返回 Exception 的消息。

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()


                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1
                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Groups> will contain all Groups of The ListView
                .WriteStartElement("Groups")

                For j As Integer = 0 To lsv.Groups.Count - 1
                    ' <Group
                    .WriteStartElement("Group")
                    .WriteAttributeString("name", lsv.Groups(j).Header)
                    ' name="xyz"
                    .WriteAttributeString("headerAlignment", lsv.Groups(j).HeaderAlignment.ToString)
                    ' headerAlignment="xyz">

                    ' Loop through the Items of the given ListView...
                    For k As Integer = 0 To lsv.Groups(j).Items.Count - 1

                        .WriteStartElement("item") ' <item
                        .WriteAttributeString("text", lsv.Groups(j).Items(k).Text) ' text="xyz"

                        ' ...and SubItems to write them to the Document
                        For l As Integer = 0 To lsv.Groups(j).Items(k).SubItems.Count - 2

                            .WriteStartElement("subItem") ' <subItem
                            .WriteAttributeString("text", lsv.Groups(j).Items(k).SubItems(l + 1).Text) ' text="xyz"

                            .WriteEndElement() ' />

                        Next

                        ' At the end of each passage in this loop, the Item-Tag will be ended to start with a new item 
                        .WriteEndElement() ' </item>
                    Next

                    ' At the end of each passage in this loop, the Group-Tag will be ended to start with a new group
                    .WriteEndElement() ' </Group>
                Next

                ' Complete the Groups-Tag to signalize that all groups have been written to the document
                .WriteEndElement() '</Groups>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

ListViewWithGroupsLoad

在加载函数中,我们使用 `XmlTextReader`。它并不比保存函数复杂多少,但我将简要解释一下。有一个循环,直到 Reader 到达已保存文档的末尾。在每次迭代中,都会查询当前元素是什么。因此,逐步读取所有值并将其添加到 ListView。

参数:

  • lsv:这是将要加载的 ListView 控件
  • loadedPath:用户文件系统上配置文件的路径
  • append:“True”如果您想将保存的内容追加到 ListView。“False”如果您想在加载前清除 ListView。
  • loadColumns:指示您是否要加载 HeaderColumn。
  • addInvisibleColumn:您是否想向 ListView 添加一个“不可见”列?也许您已经注意到无法对第一个 ColumnHeader 的文本进行对齐。所以这个选项非常有帮助。通过一个宽度为 0 像素的“不可见”第一列,您可以做到这一点。但不要忘记在 ListView.ColumnWidthChanging 事件中将其宽度设置为 0 像素以“锁定”它。

返回值
如果没有发生错误,该函数将返回一个空字符串,否则返回 Exception 的消息。

    ''' <summary>
    ''' Loads the whole content of a ListView with groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
     Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim grp As New ListViewGroup
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If

                    End If

                    ' The tag <Group> was found; it contains the name of the group and its text alignment
                    If .Name = "Group" Then

                        'If the Tag is a StartElement (<Group>, and not </Group>), the ListView is added all the saved Groups
                        If .IsStartElement Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("headerAlignment").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Set the required properties of the ListViewGroup (name and text alignment)
                            grp = New ListViewGroup(.GetAttribute("name"), align)

                            ' Add the group to the ListView
                            lsv.Groups.Add(grp)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            listItem.Group = grp
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then
                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function  

ListViewWithNoGroupsSave

该函数也 `无需解释`,因为它 `与 ListViewWithNoGroupsSave 几乎相同`。不同之处在于这里 `没有循环遍历组`;我们直接进入项目。

参数和返回值 `与` 其他函数 `相同`。

    ''' <summary>
    ''' Saves the content of a ListView control which has groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()

                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1

                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Items> will contain all Items of the ListView
                .WriteStartElement("Items")    ' <Items>

                ' Loop through the given ListView's items...
                For k As Integer = 0 To lsv.Items.Count - 1

                    .WriteStartElement("item") ' <item
                    .WriteAttributeString("text", lsv.Items(k).Text) ' text="xyz">

                    ' ...and SubItems to write them to the Document
                    For l As Integer = 0 To lsv.Items(k).SubItems.Count - 2

                        .WriteStartElement("subItem") ' <subItem
                        .WriteAttributeString("text", lsv.Items(k).SubItems(l + 1).Text)    ' text="xyz"

                        .WriteEndElement()    ' />

                    Next

                    ' At the end of each passage in the main loop, the Item-Tag will be ended to start with a new item 
                    .WriteEndElement() ' </item>
                Next

                ' Complete the Items-Tag to signalize that all items have been written to the document
                .WriteEndElement() ' </Items>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function  

ListViewWithNoGroupsLoad

该函数也 `跳过` 了 <Group> 标签,因为它们不存在。

参数和返回值 `与` 其他函数 `相同`。

    ''' <summary>
    ''' Loads the whole content of a ListView without groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
    Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then

                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

我们必须确定要保存的 ListView 是否有组!
既然我们现在知道如何处理带有组的 ListView 和不带组的 ListView,我们必须 `检测用户想要保存的 ListView 类型`。

好了,我们现在在“`ListViewContent`”类中有四个函数。确保它们被声明为“`Private Shared`”,而类本身被声明为“`Public MustInherit`”。

为什么?我稍后会在“使用代码”中解释。

现在我们需要 `两个 public shared 函数`供用户调用。在这两个函数中,我们不保存或加载任何内容,我们只是检测 ListView 控件是否有组。完成此操作后,我们将调用正确的函数。

保存

那么,我们如何检测要保存的 ListView 的类型呢?

我们可以访问 ListView 控件所需的所有信息。因此,包含某些组的 ListView 中的组数大于零。但我们也必须检查 ListView 是否显示任何组。

将其放入 Visual Basic .Net 并不难

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Save(ByVal lsv As ListView, ByVal destinationPath As String) As String

        If lsv.Groups.Count > 0 AndAlso lsv.ShowGroups Then
            Return ListViewWithGroupsSave(lsv, destinationPath)

        Else
            Return ListViewWithNoGroupsSave(lsv, destinationPath)

        End If

    End Function 

如果给定的 ListView 包含的组数大于零并且它也显示了它们,则该函数调用 `ListViewWithGroupsSave`,否则调用 `ListViewWithNoGroupsSave`。

然后返回返回值。如果没有发生错误,它将是一个空字符串,否则是 Exception 的消息。

Load (加载)

要识别要加载的文件是否包含组,会稍微困难一些。我们需要两个命名空间

  1. System.IO
  2. System.Text.RegularExpressions

首先,我们需要已保存配置文件内容。我们使用 `System.IO` 命名空间中的 `File.ReadAllText` 函数并删除所有换行符。

为了找出文件是否包含 `<group>` 标签,我们需要 `System.Text.RegularExpressions` 命名空间中的 `RegEx.IsMatch` 函数。Regex 是验证和编辑字符串的强大工具。`IsMatch` 会根据模式检查字符串的有效性。

这是我们将使用的非常简单的模式: `(<groups>.*</groups>)|(<groups />)`

  • 括号用于对两个不同的表达式进行分组
  • 此模式搜索标签 <groups>…
  • “**。**”代表任何字符,量词“**\***”表示前面的表达式(“**。**”=任何字符)可以出现任意次数。
  • 然后用 </groups> 关闭组标签
  • 管道符号(**|**)查找符号**|**之前或之后的表达式。
  • 此模式还查找空组 (<groups />)

由于使用了管道符号,如果找到 `<groups>.*</groups> 或 <groups />` 中的 `任何一个`,此函数将返回“True”。

这样我们就得到了 VB 代码

    ''' <summary>
    ''' Loads the whole content of a ListView
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Load(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
      Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String

        Dim hasFileGroups As Boolean =
         Regex.IsMatch(File.ReadAllText(loadedPath).Replace(Chr(10), ""), "(<groups>.*</groups>)|(<groups />)",
         RegexOptions.IgnoreCase)

        If hasFileGroups Then
            Return ListViewWithGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        Else
            Return ListViewWithNoGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        End If

    End Function   

如果已保存的配置文件 `包含 <group>` 标签,则该函数调用 `ListViewWithGroupsLoad`,否则调用 `ListViewWithNoGroupsLoad`。

然后返回返回值。如果没有发生错误,它将是一个空字符串,否则是 Exception 的消息。

整个类

这是“ListViewContent”类的完整代码

Option Strict On

Imports System.Text
Imports System.Xml
Imports System.IO
Imports System.Text.RegularExpressions

Public MustInherit Class ListViewContent

#Region "Private Methods for saving and loading the content of a ListView"

#Region "Save the whole content of a ListView with groups into a file"

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()


                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1
                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Groups> will contain all Groups of The ListView
                .WriteStartElement("Groups")

                For j As Integer = 0 To lsv.Groups.Count - 1
                    ' <Group
                    .WriteStartElement("Group")
                    .WriteAttributeString("name", lsv.Groups(j).Header)
                    ' name="xyz"
                    .WriteAttributeString("headerAlignment", lsv.Groups(j).HeaderAlignment.ToString)
                    ' headerAlignment="xyz">

                    ' Loop through the Items of the given ListView...
                    For k As Integer = 0 To lsv.Groups(j).Items.Count - 1

                        .WriteStartElement("item") ' <item
                        .WriteAttributeString("text", lsv.Groups(j).Items(k).Text) ' text="xyz"

                        ' ...and SubItems to write them to the Document
                        For l As Integer = 0 To lsv.Groups(j).Items(k).SubItems.Count - 2

                            .WriteStartElement("subItem") ' <subItem
                            .WriteAttributeString("text", lsv.Groups(j).Items(k).SubItems(l + 1).Text) ' text="xyz"

                            .WriteEndElement() ' />

                        Next

                        ' At the end of each passage in this loop, the Item-Tag will be ended to start with a new item 
                        .WriteEndElement() ' </item>
                    Next

                    ' At the end of each passage in this loop, the Group-Tag will be ended to start with a new group
                    .WriteEndElement() ' </Group>
                Next

                ' Complete the Groups-Tag to signalize that all groups have been written to the document
                .WriteEndElement() '</Groups>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#Region "Load the whole content of a ListView with groups from a file"

    ''' <summary>
    ''' Loads the whole content of a ListView with groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
     Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim grp As New ListViewGroup
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If

                    End If

                    ' The tag <Group> was found; it contains the name of the group and its text alignment
                    If .Name = "Group" Then

                        'If the Tag is a StartElement (<Group>, and not </Group>), the ListView is added all the saved Groups
                        If .IsStartElement Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("headerAlignment").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Set the required properties of the ListViewGroup (name and text alignment)
                            grp = New ListViewGroup(.GetAttribute("name"), align)

                            ' Add the group to the ListView
                            lsv.Groups.Add(grp)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            listItem.Group = grp
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then
                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region


#Region "Save the whole content of a ListView without groups into a file"

    ''' <summary>
    ''' Saves the content of a ListView control which has groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()

                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1

                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Items> will contain all Items of the ListView
                .WriteStartElement("Items")    ' <Items>

                ' Loop through the given ListView's items...
                For k As Integer = 0 To lsv.Items.Count - 1

                    .WriteStartElement("item") ' <item
                    .WriteAttributeString("text", lsv.Items(k).Text) ' text="xyz">

                    ' ...and SubItems to write them to the Document
                    For l As Integer = 0 To lsv.Items(k).SubItems.Count - 2

                        .WriteStartElement("subItem") ' <subItem
                        .WriteAttributeString("text", lsv.Items(k).SubItems(l + 1).Text)    ' text="xyz"

                        .WriteEndElement()    ' />

                    Next

                    ' At the end of each passage in the main loop, the Item-Tag will be ended to start with a new item 
                    .WriteEndElement() ' </item>
                Next

                ' Complete the Items-Tag to signalize that all items have been written to the document
                .WriteEndElement() ' </Items>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#Region "Load the whole content of a ListView without groups from a file"

    ''' <summary>
    ''' Loads the whole content of a ListView without groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
    Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then

                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#End Region

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Save(ByVal lsv As ListView, ByVal destinationPath As String) As String

        If lsv.Groups.Count > 0 AndAlso lsv.ShowGroups Then
            Return ListViewWithGroupsSave(lsv, destinationPath)

        Else
            Return ListViewWithNoGroupsSave(lsv, destinationPath)

        End If

    End Function

    ''' <summary>
    ''' Loads the whole content of a ListView
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Load(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
      Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String

        Dim hasFileGroups As Boolean =
         Regex.IsMatch(File.ReadAllText(loadedPath).Replace(Chr(10), ""), "(<groups>.*</groups>)|(<groups />)",
         RegexOptions.IgnoreCase)

        If hasFileGroups Then
            Return ListViewWithGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        Else
            Return ListViewWithNoGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        End If

    End Function

End Class

Using the Code

在“我们必须确定要保存的 ListView 是否有组!”中,我说过类中必须有 `四个 private shared 函数` 和 `两个 public shared 函数`,而类本身必须声明为 `MustInherit`。现在我将解释原因。

当您有一个类时,您通常需要实例化一个对象才能访问其函数。但是当您将它们声明为“`shared`”时,您就不需要了。并且该类被声明为“`MustInherit`”意味着不能从此类创建对象,您必须将其用作基类。

以下是一些如何使用函数的示例

  • 将内容保存到用户桌面上的名为“config.smp”的文件中
ListViewContent.Save(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"))
  • 追加当前保存的文件并在 ListView 中添加一个不可见列
ListViewContent.Load(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"), True, True, True) 
  • 追加当前保存的文件而不加载 ColumnHeaders
ListViewContent.Load(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"), True, False)      

示例应用

我还编写了一个演示应用程序来展示如何使用我的 ListViewContent 类。

save.png

在此程序中,您可以向 ListView 控件添加组和项目。如果您在 ComboBox 中选择“-none-”项,那么 ListView 中的组将被 `隐藏`(将其属性“`ShowGroups`”设置为“`False`”)。

使用添加不可见 HeaderColumn 的选项加载后,您可以选中 CheckBox 以居中文本的所有 HeaderColumns。

load.png

历史

  • 2012 年 2 月 23 日 – 发布第一个版本。
© . All rights reserved.