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

构建您自己的 Visual Studio:用于运行时对象编辑的应用程序框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (19投票s)

2006年9月12日

26分钟阅读

viewsIcon

68128

downloadIcon

1179

本文介绍了一个通用的应用程序框架,可能对需要类似 Visual Studio 界面项目有所帮助。该应用程序演示了提供工具箱、工作区、对象树视图和对象编辑器的方法。

Sample application

引言

本文介绍了一个通用的应用程序框架,可能对需要类似 Visual Studio 界面项目有所帮助。该应用程序演示了提供工具箱、工作区、对象树视图和对象编辑器的方法。

该应用程序框架允许用户通过将项目从工具箱拖放到工作区来创建对象;用户可以在工作区中拖动对象以重新定位它们,用户可以选择工作区中的对象并使用属性编辑器更改对象的属性。属性编辑器中的更改将立即反映在对象的显示和树视图中。树视图显示所有工作区对象以及子对象。用户还可以从树视图中选择对象,将其推入属性编辑器或从项目中删除。用户可以将工作区保存为自定义文件类型,并使用应用程序的文件打开命令将自定义文件恢复到工作区中,以便后续查看和编辑。

该应用程序本身没有价值,它只是为了演示构建需要包含这些类型功能的有用应用程序的方法。该应用程序仅旨在演示构建这种界面的方法;它不是唯一的实现方式,但它代表了一种简单的方式来构建一个相当强大的界面,许多用户会因为使用类似产品而熟悉它。

该应用程序并不完整,因为项目中没有描述和机械化许多典型功能;例如,我没有解决打印或对象对齐和分布等问题。它旨在作为更复杂应用程序的基础,但它本身不是一个完整或复杂的应用程序。正如我所描述的,您可以通过多种不同方式更改方法,仍然可以实现相同类型的最终界面。例如,我将演示使用按钮作为工具箱中的对象,但实际上,任何支持拖放的对象都可以用于代替按钮。

本应用程序和文章将尝试演示以下概念

  • 拖放以支持应用程序工具箱界面
  • 面板拖放以支持在运行时移动现有对象
  • 树视图控件用于提供所有现有父对象和子对象的映射
  • 树视图控件用于允许对象选择
  • 树视图控件用于通过简单递归删除项目对象和子对象
  • 属性网格控件用于编辑对象属性
  • 序列化用于存储包含重建文件之间所需所有必要数据的文件(通过自定义文件类型实现集体对象持久化)
  • 反序列化用于恢复存储的、序列化的对象以支持文件打开功能,并随后用于将存储的工作区恢复到活动应用程序
  • 处理命令行参数以在初始化后立即打开文件
  • 通过安装和部署包创建自定义文件类型

入门

要开始,请解压缩附件并将解决方案加载到 Visual Studio 2005 中。检查解决方案资源管理器,并注意项目中包含的文件

图 1:显示项目文件的解决方案资源管理器

在解决方案标题节点下方,有两个已添加的文件夹,一个包含图形,一个包含类。`classes` 文件夹包含四个类;其中三个是用户控件,一个是标准类文件。这些类仅用于演示目的,并构成了应用程序中使用的对象和对象数据的基础。这三个用户控件是:`Animal.vb`、`Person.vb` 和 `Place.vb`。其余的类 (`ObjectNote.vb`) 是标准类。

`graphics` 文件夹包含应用程序中使用的图标和图像集合。

在解决方案根目录中,请注意有两个文件:`FileSerialize.vb` 是一个用于处理应用程序中序列化和反序列化的模块。其余的类是 `frmMain.vb`;这个类是主应用程序,它包含用于驱动应用程序的大部分代码。

应用程序的主窗体 (`frmMain.vb`) 的布局包括一个左侧面板,其中包含一个选项卡式面板;该选项卡式面板有两个选项卡:工具箱和属性。“工具箱”面板包含三个由按钮表示的用户控件;这些按钮可以拖到窗体上,每当控件拖到窗体上时,关联类型的控件都会添加到工作区面板控件的控件集合以及一个包含对添加到面板的这些对象的引用的排序列表中。“属性”选项卡用于公开树视图控件和属性网格控件。树视图控件显示当前项目中包含的所有对象;属性网格显示从工作区(面板控件)或树视图控件中选择的任何对象的属性。用户可以编辑公开的任何属性,但应用程序仅配置为持久化类中定义的自定义属性;这可以修改以支持持久化与对象关联的任何或所有值,但为了使代码简洁易读,我选择仅支持序列化和反序列化中的少数属性。

请查看图 2 和图 3,了解演示应用程序运行的示例;图 2 显示了选中“工具箱”选项卡的应用程序;“工具箱”面板中的对象可以拖放到右侧的面板上;在此示例中,您可以看到已拖放到面板上的几个对象。图 3 显示了应用程序中选中“属性”选项卡,顶部的树视图显示了工作区中当前所有对象。属性网格控件设置为允许对最后选择的对象进行编辑。每次从树视图或面板中选择新对象时,属性网格都会更新以显示该选定对象的属性。

图 2:应用程序运行,选中“工具箱”选项卡

图 3:应用程序运行,选中“属性”选项卡

代码:用户控件

解决方案的类文件夹中包含三个用户控件。它们基本上都是以相同的方式构建的,所以我只描述其中一个,如果您愿意,可以查看其他两个;用户控件与此讨论无关;应用程序框架可用于编辑任何对象的属性;我包含的控件仅用于此演示。

打开 `Animal.vb` 类文件。这是一个用户控件;用户控件不易序列化,因此需要额外的工作来持久化它们并在每次使用后重新激活它们。

每个用户控件都以几个导入开始;这些导入用于在使用时为控件添加设计支持。该类以以下代码开头

Imports System.ComponentModel
Imports System.ComponentModel.EditorAttribute
Imports System.ComponentModel.Design.DesignerCollection

Public Class Animal

在类声明之后,定义了一个名为 `Declarations` 的区域,并填充了私有成员变量和枚举。我包含了枚举来演示属性网格如何根据枚举显示选项(它们在属性编辑器中的组合框中公开);声明区域内容如下

#Region "Declarations"

    Private mUniqueID As Guid
    Private mDisplayName As String
    Private mSpecies As String
    Private mType As AnimalType
    Private mHabitat As HabitatType
    Private mColor As System.Drawing.Color
    Private mLocation As Point


    Public Enum AnimalType
        Mammal
        Amphibeon
        Fish
        Bird
        Reptile
    End Enum

    Public Enum HabitatType
        Water
        Desert
        Forest
        Arid
        Mountain
        Plain
        Coastal
    End Enum

#End Region

在声明区域之后,“`New`”子例程被重载;一个版本的子例程用于创建控件的新实例,而另一个版本用于在反序列化后重新创建控件

    ' Default Constructor
    Public Sub New()

        InitializeComponent()

        Dim newGuid As Guid = Guid.NewGuid
        Me.mUniqueID = newGuid
        Habitat = HabitatType.Arid
        Type = AnimalType.Mammal
        DisplayName = "myAnimal"

    End Sub

这是用于创建控件新实例的默认构造函数;它为控件设置一个唯一的 ID,该 ID 用于管理树视图中的关系,并为控件设置一些默认属性。接下来是备用构造函数

    ' Alternate constructor
    Public Sub New(ByVal theId As Guid, _
                    ByVal theDisplayName As String, _
                    ByVal theSpecies As String, _
                    ByVal theType As String, _
                    ByVal theHabitat As String, _
                    ByVal theColor As System.Drawing.Color, _
                    ByVal theLocation As Point)

        InitializeComponent()

        ' when restoring from data after deserialization, set the UID to
        ' the stored value and the recovered properties for the control
        ' including its location are stored into a new instance of the 
        ' user control
        mUniqueID = theId
        DisplayName = theDisplayName
        Species = theSpecies
        Type = theType
        Habitat = theHabitat
        MajorColor = theColor
        Me.Location = theLocation

    End Sub

这是用于在反序列化后恢复控件的备用构造函数;提供给构造函数的值将由一个结构提供,该结构在序列化时由原始控件的属性填充。可以很容易地添加额外的属性;但是,为了保持简单,我选择只支持少数可用属性,并且只支持用户控件类中的一个属性(位置)。如果您想添加其他属性,例如边框样式或背景颜色,您可以在序列化时捕获这些值,并在通过此备用构造函数重建控件时将其发布回控件。

其余的 `Animal` 类包含控件使用的属性。在查看属性时,检查分配给可在属性编辑器控件中编辑的每个属性的属性。该区域如下

#Region "Properties"

    Public ReadOnly Property ID() As Guid
        Get
            Return mUniqueID
        End Get
    End Property

    Public Property MyLocation() As Point
        Get
            Return mLocation
        End Get
        Set(ByVal value As Point)
            mLocation = value
        End Set
    End Property

    <CategoryAttribute("Animal Information"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The display name of this animal object.")>_
    Public Property DisplayName() As String
        Get
            Return mDisplayName
        End Get
        Set(ByVal value As String)
            mDisplayName = value
            txtTitle.Text = "ANIMAL: " & mDisplayName
        End Set
    End Property

    <CategoryAttribute("Animal Information"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The species of this animal object.")>_
    Public Property Species() As String
        Get
            Return mSpecies
        End Get
        Set(ByVal value As String)
            mSpecies = value
            lblSpecies.Text = "Species: " & mSpecies
        End Set
    End Property

    <CategoryAttribute("Animal Information"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The type of this animal object.")>_
    Public Property Type() As AnimalType
        Get
            Return mType
        End Get
        Set(ByVal value As AnimalType)
            mType = value
            lblType.Text = "Type: " & mType.ToString()
        End Set
    End Property

    <CategoryAttribute("Animal Information"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The habitat used by this animal object.")>_
    Public Property Habitat() As HabitatType
        Get
            Return mHabitat
        End Get
        Set(ByVal value As HabitatType)
            mHabitat = value
            lblHabitat.Text = "Habitat: " & mHabitat.ToString()
        End Set
    End Property

    <CategoryAttribute("Animal Information"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The primary color of this animal object.")>_
    Public Property MajorColor() As System.Drawing.Color
        Get
            Return mColor
        End Get
        Set(ByVal value As System.Drawing.Color)
            mColor = value
            lblColor.Text = "Color: " & mColor.ToString()
        End Set
    End Property

#End Region

就属性而言,`Category` 属性用于在属性编辑器中对属性进行分组。如果三个属性具有共同的 `Category` 属性,它们将在编辑期间显示为一组。`Browsable` 属性决定属性是否将出现在属性编辑器中。`Description` 属性提供显示在编辑器底部的文本字符串;这通常用于向用户提供说明或描述属性。

图 4:正在使用的属性网格

这几乎涵盖了用户控件的内容;同样,所有三个基本上都是以相同的方式构建的,因此,我只描述了三个用户控件中的一个。

代码:可序列化类

类文件夹包含另一个类;它被称为 `ObjectNote.vb`,它是一个简单的可序列化类。它被包含在演示中有两个原因。第一个原因是可序列化类中的所有内容都可以序列化(您可能猜到会这样),因此您无需进行任何额外操作即可在每次使用后序列化、反序列化或重建对象。我将其包含在演示中的另一个原因是因为我使用它来演示从树视图控件添加和删除子节点。

该类非常简单,只包含三个值得注意的属性:它的唯一 ID,它父级的唯一 ID,以及一个用于保存注释本身的字符串。在运行时,如果用户右键单击树视图或面板中的对象,应用程序将显示一个上下文菜单,其中两个菜单选项之一是向对象添加注释。这是保存该注释的容器类。

代码以几个导入和一个类声明开头。请注意,该类标记为可序列化

Imports System.ComponentModel
Imports System.ComponentModel.EditorAttribute
Imports System.ComponentModel.Design.DesignerCollection
Imports System.Runtime.Serialization

<Serializable()>_
Public Class ObjectNote

在导入和类声明之后,定义了一个默认构造函数,如下所示

    ' Default Constructor
    Public Sub New(ByVal parent As Guid)

        ' In order to keep track of who this note belongs to when displayed
        ' in the treeview control, we need to know the identity
        ' of the parent object (node)
        ' Here we give the new object a new guid and set its parent property
        ' to the value passed to the constructor.
        Dim newGuid As Guid = Guid.NewGuid
        Me.mUniqueID = newGuid
        Me.mParentID = parent

    End Sub

根据代码中的注释,构造函数传入其父级的唯一 ID (GUID),并设置其唯一 ID 和父 ID 属性。父 ID 用于执行两件事:维护树视图中存储对象之间的关系,并允许在用户删除主节点时查找和删除子对象。

在构造函数之后,在一个区域内进行了一些声明;该部分如下所示

#Region "Declarations"

    Private mUniqueID As Guid
    Private mParentID As Guid
    Private mDisplayName As String
    Private mNote As String

#End Region

这些私有成员变量在控件创建时设置,或由接下来添加的属性设置

#Region "Properties"

    Public ReadOnly Property ID() As Guid
        Get
            Return mUniqueID
        End Get
    End Property


    Public Property MyParent() As Guid
        Get
            Return mParentID
        End Get
        Set(ByVal value As Guid)
            mParentID = value
        End Set
    End Property

    <CategoryAttribute("Object Note"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("Enter text to describe the associated object.")>_
    Public Property Note() As String
        Get
            Return mNote
        End Get
        Set(ByVal value As String)
            mNote = value
        End Set
    End Property

    <CategoryAttribute("Object Note"), _
    Browsable(True), _
    BindableAttribute(False), _
    DescriptionAttribute("The display name of this note object.")>_
    Public Property DisplayName() As String
        Get
            Return mDisplayName
        End Get
        Set(ByVal value As String)
            mDisplayName = value
        End Set
    End Property

#End Region

在所示的属性中,已经提到了唯一 ID 和父 ID。显示名称是注释在运行时显示的名称,而 `Note` 属性是分配给父节点的注释。

这总结了应用程序使用的控件。再次强调,这些类的定义只是为了说明此演示中使用的方法;除了演示之外,它们没有任何有用的目的(除非您有一些不寻常的需求,并且恰好需要控件来监控人、动物和地点)。

代码:主窗体

主窗体布局

主窗体顶部包含一个菜单。一个选项卡式控件停靠在左侧,有两个面板;一个用作工具箱,另一个用于显示树视图和属性。选项卡式面板的右侧有一个垂直拆分器;在属性选项卡中,该面板水平拆分,树视图停靠在顶部,水平拆分器位于树视图下方,属性网格控件设置为完全停靠在水平拆分器下方。在拆分器的右侧,一个工具栏停靠在顶部,一个面板设置为完全停靠在工具栏下方。该面板的背景颜色设置为白色,并用作对象的容器。

鉴于面板控件将成为从工具栏拖动的对象的目标,面板的 `AllowDrop` 属性必须设置为 True

三个用户控件中的每一个都由一个放置在工具箱中的按钮表示。每个按钮都有一个图像和一个描述它的文本标签。

工具栏添加了两个按钮;一个名为“选择”,另一个名为“拖动或移动”。如果用户处于选择模式,单击面板中的项目将选择它并使控件在属性网格中处于活动编辑状态;如果用户处于拖动模式,可以使用鼠标在面板上拖动对象以重新定位它们。

除了视觉元素之外,窗体还包含一个文件打开对话框、一个文件保存对话框和一个包含两个选项的上下文菜单:删除选定元素和向选定元素添加注释。还有一个工具提示和主菜单条。

主菜单有一个文件菜单,其中包含创建新文件、打开现有文件、保存文件和退出应用程序的选项。

主窗体代码

主窗体的代码分为几个区域;这些区域如下:`Declarations`、Drag and Drop – Mouse Down HandlersDrag and Drop – Drop Handlers 和 `Methods`。

`Declarations` 区域包含应用程序中使用的一些成员变量,该部分如下所示

#Region "Declarations"

    ' Private member variable declarations
    Private mCurrentObjectName As String
    Private mCurrentObject As Object
    Private mObjectCollection As New SortedList
    Private mObjectNotes As New SortedList
    Private mObjectFileBundle As New SortedList
    Private mSelect As Boolean = False

    ' Data Containers for Serialization --
    ' This example uses three user controls for the basis of the
    ' objects needed to demonstrate the concept; these are not
    ' serializable and in order to persist them, you need to capture
    ' the user control’s properties into a serializable construct and
    ' store that instead of the control itself. The following
    ' three structs are used to contain the user control data.
    ' if you wish to persist other data from the control such as the 
    ' background color, font, etc., you would need to add additional
    ' items to these data container structs and populate and recover 
    ' those values at runtime during the save and open file operations
    <Serializable()> _
    Private Structure AnimalData
        Public theId As Guid
        Public theDisplayName As String
        Public theSpecies As String
        Public theType As String
        Public theHabitat As String
        Public theColor As System.Drawing.Color
        Public theLocation As Point
    End Structure

    <Serializable()> _
    Private Structure PersonData
        Public theId As Guid
        Public theDisplayName As String
        Public thePersonName As String
        Public theBirthPlace As String
        Public theDateOfBirth As Date
        Public theOccupation As String
        Public theLocation As Point
    End Structure


    <Serializable()> _
    Private Structure PlaceData
        Public theId As Guid
        Public theDisplayName As String
        Public thePlaceName As String
        Public theCity As String
        Public theState As String
        Public theCountry As String
        Public theLocation As Point
    End Structure

#End Region

私有成员变量用于跟踪当前选定的对象和该对象的名称。还有两个其他集合(排序列表)用于包含所有用户控件和所有注释。一个额外的排序列表用于将所有对象数据和注释打包,以便序列化为具有自定义扩展名的单个文件。

声明成员变量后,定义了三个可序列化结构:每个用户控件一个。由于我们不打算尝试序列化用户控件中的所有内容,因此我们只添加了几个关键属性。如前所述,很容易从用户控件中添加额外的属性,并在每次使用后序列化和反序列化这些属性。

Drag and Drop – Mouse Down 区域中,三个按钮中的每一个都设置为支持拖放操作。为每个按钮编写了一个处理程序,但由于它们基本相同,我只在此处显示一个

    Private Sub Button1_MouseDown(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.MouseEventArgs) _
            Handles Button1.MouseDown

        ' When the user clicks down on the button, we assume we are going to 
          drag one to the panel so we instance an animal, set its default 
          properties and make the DoDragDrop call. The control is not yet 
          added to the panel's control collection
        ' and is not yet included in any data that will be serialized; the 
          user has to complete the drop onto the panel for this transaction 
          to complete.
        Dim myAnimal As New Animal
        mCurrentObjectName = "Animal"
        mCurrentObject = myAnimal
        myAnimal.DisplayName = "myAnimal"
        Button1.DoDragDrop(myAnimal, DragDropEffects.All)

    End Sub

鼠标按下事件用于实例化关联的类,在对象上设置几个属性,并通过将新对象传递给按钮的 `DoDragDrop` 方法并将 `DragDropEffect` 设置为 `All` 来调用该方法。这就设置了拖动端,现在我们需要处理放置端。查看放置处理程序区域,您将看到定义了两个子例程,用于准备面板控件以接收放置的项目:`Panel1_DragDrop` 和 `Panel1_DragEnter`;它们如下所示

Private Sub Panel1_DragDrop1(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DragEventArgs) _
        Handles Panel1.DragDrop

        ' Get the current mouse position and define a point, then
        ' convert the point to work on the panel control (within its 
          coordinates)
        Dim dropX As Single = e.X
        Dim dropY As Single = e.Y
        Dim dropLocation = New Point(dropX, dropY)
        Dim dropPoint As New Point()
        dropPoint = Panel1.PointToClient(dropLocation)

        ' Create new objects based upon what has been dropped onto the panel
        ' and set the user control's handlers to work with the dropped 
          object.
        Select Case mCurrentObjectName.ToString()

            Case "Animal"
                Dim myAnimal As New Animal
                myAnimal = CType(Me.mCurrentObject, Animal)
                Panel1.Controls.Add(myAnimal)
                myAnimal.Location = dropPoint
                myAnimal.ContextMenuStrip = ContextMenuStrip1
                mObjectCollection.Add(myAnimal.ID, myAnimal)
                AddHandler myAnimal.MouseClick, AddressOf Object_MouseClick
                AddHandler myAnimal.MouseDown, AddressOf Object_MouseDown
            Case "Person"
                Dim myPerson As New Person
                myPerson = CType(Me.mCurrentObject, Person)
                Panel1.Controls.Add(myPerson)
                myPerson.Location = dropPoint
                myPerson.ContextMenuStrip = ContextMenuStrip1
                mObjectCollection.Add(myPerson.ID, myPerson)
                AddHandler myPerson.MouseClick, AddressOf Object_MouseClick
                AddHandler myPerson.MouseDown, AddressOf Object_MouseDown
            Case "Place"
                Dim myPlace As New Place
                myPlace = CType(Me.mCurrentObject, Place)
                Panel1.Controls.Add(myPlace)
                myPlace.Location = dropPoint
                myPlace.ContextMenuStrip = ContextMenuStrip1
                mObjectCollection.Add(myPlace.ID, myPlace)
                AddHandler myPlace.MouseClick, AddressOf Object_MouseClick
                AddHandler myPlace.MouseDown, AddressOf Object_MouseDown
            Case Else
                mCurrentObject.Location = dropPoint
        End Select

        ' With a new item dropped into the panel, update the treeview to
        ' reflect the latest addition to the node collection
        UpdateTreeview()

    End Sub

此处理程序的第一部分获取放置时的当前鼠标位置,并将其转换为面板控件可用的值。这将确保放置项目的左上角位于鼠标放置时指示的精确位置。位置转换后,子例程在 `Select Case` 语句中评估当前对象的名称(在按钮接收到鼠标按下命令时设置);根据当前对象,子例程然后创建适当对象类型的实例,将其设置为当前对象,将控件添加到面板,将其位置设置为放置点,然后将上下文菜单和鼠标单击以及鼠标按下事件处理程序添加到控件。

一旦对象被放置并添加到面板中,树视图控件就会更新以显示新添加的对象。`DragEnter` 事件处理程序设置为处理拖放是否将用于移动控件或添加新控件。

    Private Sub Panel1_DragEnter1(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.DragEventArgs) _
            Handles Panel1.DragEnter

        ' Setup to either copy or move the dragged control.
        If (e.Data.GetDataPresent(DataFormats.Text)) Then
            e.Effect = DragDropEffects.Copy
        Else
            e.Effect = DragDropEffects.Move
        End If

    End Sub

要描述的最后一段代码包含在 `Methods` 区域中;在此区域中,定义了十五个独立的子例程。其中一些子例程非常简单,几乎不值得一提,而另一些则需要一些解释。

`Methods` 区域中的前两个子例程是两个通用处理程序:一个鼠标按下处理程序和一个鼠标单击处理程序。

    Private Sub Object_MouseClick(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.MouseEventArgs)

        ' A generic mouse click handler used for dynamically added user 
          control objects. This sets the current object, current object 
          name, and object associated with the property grid in response to a 
          click
        Try

            Me.mCurrentObject = sender
            Me.mCurrentObjectName = sender.DisplayName.ToString()
            Me.PropertyGrid1.SelectedObject = sender

        Catch ex As Exception

        End Try

    End Sub 
    Private Sub Object_MouseDown(ByVal sender As Object, ByVal e As 
    System.Windows.Forms.MouseEventArgs)

        ' A generic mouse down handler used for dynamically added user 
          control objects.
        ' This works such that if the user to swap between selecting a 
          control to load
        ' it into the property grid control, or to allow the user to drag a 
          control to a new location on the form panel.
        Try

            If mSelect = True Then

                mCurrentObject = sender
                mCurrentObjectName = sender.DisplayName.ToString()
                mCurrentObject.DoDragDrop(mCurrentObject, 
                DragDropEffects.All)
                Me.PropertyGrid1.SelectedObject = sender

            End If

        Catch ex As Exception

        End Try

    End Sub

这两个子例程中提供的注释定义了处理程序的用途。通常,这些处理程序在运行时添加到添加到面板控件的任何控件的新实例中。

接下来的两个子例程用于设置一个 `Boolean` 值,用于允许应用程序确定它是否处于选择或拖动模式。此代码是两个工具栏控件的 `Click` 事件处理程序。该代码如下所示

    Private Sub tbnSelect_Click_1(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles tbnSelect.Click

        ' Select mode is used allow the user to click on an object without 
          dragging
        mSelect = False

    End Sub

    Private Sub tbnDrag_Click_1(ByVal sender As System.Object, ByVal e As 
    System.EventArgs) Handles tbnDrag.Click

        ' With select mode disabled the user can drag objects around to move 
          them
        mSelect = True

    End Sub

继续,接下来是更新树视图的调用。每当编辑属性网格项或从当前工作区添加或删除项时,都会进行此调用。该代码如下所示

    Private Sub UpdateTreeview()

        ' Clears and reloads all of the nodes in the treeview
        ' based upon object data stored in the objects sorted list
        ' This first loads the objects, and then checks for and loads
        ' any notes associated with the current object.
        TreeView1.Nodes.Clear()

        Dim de As DictionaryEntry
        For Each de In mObjectCollection
            Dim nd As New TreeNode
            nd.Name = de.Value.displayname.ToString()
            nd.Text = de.Value.displayname.ToString()
            nd.Tag = de.Value.ID.ToString()
            Me.TreeView1.Nodes.Add(nd)
            ' add any notes
            Dim de2 As DictionaryEntry
            For Each de2 In mObjectNotes
                If de2.Value.MyParent.ToString() = nd.Tag.ToString() Then
                    Dim newNoteNode As New TreeNode
                    newNoteNode.Text = "Note"
                    newNoteNode.Tag = de2.Value.ID.ToString()
                    nd.Nodes.Add(newNoteNode)
                End If
            Next
        Next

        ' The treeview will normally collapse all nodes in response to a 
          change, this call forces all nodes to open.
        TreeView1.ExpandAll()

    End Sub

这段代码非常简单;它清除当前树视图中的所有项目,然后循环遍历对象集合以恢复树视图。随着项目从对象集合中添加和删除,树视图将仅反映工作区的当前状态。

同样简单的是属性网格的 `PropertyValueChanged` 处理程序,它只是调用树视图进行更新

    Private Sub PropertyGrid1_PropertyValueChanged(ByVal s As Object, _
            ByVal e As System.Windows.Forms.PropertyValueChangedEventArgs) _
            Handles PropertyGrid1.PropertyValueChanged

        ' Whenever the user updates a property in the property editor, the 
          object will update
        ' automatically but the treeview will not; this call will update the 
          treeview in response
        ' to dynamic edits to the property grid control.
        UpdateTreeview()

    End Sub

每当用户从树视图控件中选择一个项目时,属性网格控件都需要更新以显示所选项目关联的对象以供编辑;此代码实现了这一点

    Private Sub TreeView1_AfterSelect(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.TreeViewEventArgs) _
            Handles TreeView1.AfterSelect

        ' Whenever the user clicks on a treeview node, this subroutine will
        ' select the object associated with the treeview node into the 
          property
        ' grid and make it available for edits.

        ' The first check is for user control objects
        Dim de As DictionaryEntry
        For Each de In mObjectCollection

            If de.Key.ToString = e.Node.Tag.ToString() Then
                Me.mCurrentObject = de.Value
                Me.mCurrentObjectName = de.Value.DisplayName.ToString()
                PropertyGrid1.SelectedObject = de.Value
            End If

        Next

        ' The second check is for notes
        Dim de2 As DictionaryEntry
        For Each de2 In mObjectNotes

            If de2.Key.ToString = e.Node.Tag.ToString() Then
                Me.mCurrentObject = de2.Value
                Me.mCurrentObjectName = de2.Value.DisplayName.ToString()
                PropertyGrid1.SelectedObject = de2.Value
            End If

        Next

    End Sub

如您所见,在用户从树视图控件中选择一个项目后,应用程序将循环遍历对象集合和注释集合以查找匹配项。找到匹配项后,控件将更新以显示所选对象的属性。

删除选定项的调用以类似的方式工作

    Private Sub DelectCurrentObjectToolStripMenuItem_Click(ByVal sender _
            As System.Object, ByVal e As System.EventArgs) Handles _
            DelectCurrentObjectToolStripMenuItem.Click
    
        ' This function will delete the selected object and treeview node
        ' in response to a user request to delete the object. The option to 
          delete an object is presented in the context menu.
        Dim de As DictionaryEntry
        For Each de In mObjectCollection
            If de.Value.ID.ToString = mCurrentObject.ID.ToString() Then
                mObjectCollection.Remove(de.Key)
                DeleteOrphanNotes(de.Key)
                Exit For
            End If
        Next
    
        UpdateTreeview()
        Me.Panel1.Controls.Remove(mCurrentObject)
    
    End Sub

在这段代码中,当用户从上下文菜单中选择“删除当前对象”选项(右键单击树视图或面板控件中的一个对象后)时,系统会遍历对象集合,搜索与所选(当前)对象的匹配项。找到匹配项后,会发生两件事:首先,更新集合以删除当前对象;其次,将刚刚删除的对象的唯一 ID 传递给名为 `DeleteOrphanNotes` 的函数。`DeleteOrphanNotes` 函数用于查找分配给已删除对象的任何和所有注释,并将其从对象注释集合中删除。更改完成后,树视图控件会更新,面板的控件集合也会更新以从该集合中删除当前对象。

接下来要检查的代码用于向当前选定的对象添加新注释。执行此代码时,将创建一个新注释对象,并且其父 ID 将设置为当前对象的唯一 ID。添加注释后,树视图将更新以显示附加到其父节点的新子节点。该代码如下所示

Private Sub AddNoteToolStripMenuItem_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles AddNoteToolStripMenuItem.Click

    ' The option to add a note to a selected object (either from the 
      treeview or panel)
    ' is supported here; the control option appears in the context menu.
    Dim newNote As New ObjectNote(mCurrentObject.ID)
    newNote.DisplayName = "Note"
    Me.mObjectNotes.Add(newNote.ID, newNote)
    UpdateTreeview()
End Sub

接下来是 `DeleteOrphanNotes` 子例程。此函数将继续搜索子注释,直到不再找到为止。为了找到一个节点,该函数将再次调用自身以查找父 ID 与传递给函数的参数匹配的注释。该过程将继续递归操作,直到找不到搜索词。该代码如下所示

    Public Sub DeleteOrphanNotes(ByVal parentID As Guid)

        ' Recursively deletes all child note objects associated with
        ' a parent that has been deleted. The code continues to look
        ' for notes with a matching parent ID until no more are found; at 
        ' that point the search will cease.
        Dim blnFound As Boolean = False

        Dim de As DictionaryEntry
        For Each de In Me.mObjectNotes
            If de.Value.MyParent = parentID Then
                mObjectNotes.Remove(de.Key)
                blnFound = True
                Exit For
            End If
        Next

        If blnFound = False Then
            Exit Sub
        Else
            DeleteOrphanNotes(parentID)
        End If

    End Sub

接下来是主菜单项的 `Click` 事件处理程序。此代码用于处理新项目请求,它所做的只是调用另一个名为 `ClearAll` 的子例程。该代码和 `ClearAll` 的代码如下所示

    Private Sub NewToolStripMenuItem_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles NewToolStripMenuItem.Click

        ' Reset all collections and controls to a default
        ' empty state
        ClearAll()

    End Sub

    Private Sub ClearAll()

        ' This just clears and empties everything out of the project
        mCurrentObjectName = ""
        mCurrentObject = New Object()
        mObjectCollection.Clear()
        mObjectNotes.Clear()
        mObjectFileBundle.Clear()
        mSelect = False
        Panel1.Controls.Clear()
        TreeView1.Nodes.Clear()
        PropertyGrid1.SelectedObject = Nothing

    End Sub

如果你看一下 `ClearAll` 子例程,你会注意到它清空了所有的对象集合、对象选择和面板控件的 `Controls` 集合。它还清空了树视图控件,并将属性网格控件当前选定的对象设置为 Nothing。这段代码被放在一个单独的子例程中,因为它在响应“新建”菜单选项选择以及从文件系统打开文件时都会被调用。

继续下一个菜单选项“打开”,这段代码用于从文件系统打开文件并将其内容加载到应用程序中。“打开”菜单项的代码如下所示

    Private Sub OpenToolStripMenuItem_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles OpenToolStripMenuItem.Click

        ' Configure the appearance and file association used by the 
        ' openfiledialog box; limit the files that may be opened to the
        ' custom file type extension. BXS is arbitrary, you can make up
        ' any file extension (well, don't use something like .doc, .txt, or 
          .bmp)
        OpenFileDialog1.Title = "Open BXS Document"
        OpenFileDialog1.Filter = "BXS Documents (*.bxs)|*.bxs"

        ' if the user cancels, close out the dialog and return to the form
        If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.Cancel 
        Then

            Exit Sub

        End If

        ' Exit if no BXS document is selected
        Dim sFilePath As String
        sFilePath = OpenFileDialog1.FileName
        If sFilePath = "" Then Exit Sub

        ' Verify that the requested file is in place and
        ' exit the subroutine if the file does not exist.
        If System.IO.File.Exists(sFilePath) = False Then
            Exit Sub
        End If

        ' Since we have a valid source file, clear all is called
        ' to empty the applications sorted lists and to clear the
        ' panel of controls
        ClearAll()

        mObjectFileBundle = New SortedList
        mObjectFileBundle = FileSerializer.Deserialize(sFilePath)

        Dim ObjectData As New SortedList

        ' Recover the object collections to populate the local
        ' sortedlists used to hold the objects.
        Dim de As DictionaryEntry
        For Each de In mObjectFileBundle
            If de.Key.ToString() = "TheObjects" Then
                ObjectData = de.Value
            ElseIf de.Key.ToString() = "TheNotes" Then
                mObjectNotes.Clear()
                mObjectNotes = de.Value
            End If

        Next

        ' Add the objects back into the panel by recovering the stored
        ' object data and creating a new set of controls with the properties
        ' set to the object data gathered from the original object during
        ' file save and serialization
        Dim de2 As DictionaryEntry
        For Each de2 In ObjectData
            Select Case de2.Value.GetType.ToString()
                Case "ObjectManager.frmMain+AnimalData"
                    Dim ast As New AnimalData
                    ast = de2.Value
                    Dim animl As New Animal(ast.theId, _
                                            ast.theDisplayName, _
                                            ast.theSpecies, _
                                            ast.theType, _
                                            ast.theHabitat, _
                                            ast.theColor, _
                                            ast.theLocation)
                    animl.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(animl.ID, animl)
                    AddHandler animl.MouseClick, AddressOf Object_MouseClick
                    AddHandler animl.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(animl)
                Case "ObjectManager.frmMain+PersonData"
                    Dim pst As New PersonData
                    pst = de2.Value
                    Dim pers As New Person(pst.theId, _
                                           pst.theDisplayName, _
                                           pst.thePersonName, _
                                           pst.theBirthPlace, _
                                           pst.theDateOfBirth, _
                                           pst.theOccupation, _
                                           pst.theLocation)
                    pers.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(pers.ID, pers)
                    AddHandler pers.MouseClick, AddressOf Object_MouseClick
                    AddHandler pers.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(pers)
                Case "ObjectManager.frmMain+PlaceData"
                    Dim pls As New PlaceData
                    pls = de2.Value
                    Dim plc As New Place(pls.theId, _
                                         pls.theDisplayName, _
                                         pls.thePlaceName, _
                                         pls.theCity, _
                                         pls.theState, _
                                         pls.theCountry, _
                                         pls.theLocation)
                    plc.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(plc.ID, plc)
                    AddHandler plc.MouseClick, AddressOf Object_MouseClick
                    AddHandler plc.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(plc)
                Case Else
                    ' do nothing
            End Select
        Next

        ' update the treeview to show the objects loaded from a file
        UpdateTreeview()

    End Sub

此代码注释得很清楚,但总的来说,该选项用于打开“打开文件对话框”;该对话框配置为显示与我们正在使用的自定义文件扩展名(`.bxs`)匹配的文件。当用户选择有效文件时,该文件的路径将作为字符串捕获并传递给反序列化处理程序代码;此代码从序列化包中恢复本地文件包对象(该包是一个排序列表,其中包含另外两个排序列表,一个用于控件对象数据,一个用于注释对象)。

检查捆绑的排序列表,并使用序列化对象排序列表和对象注释排序列表来填充本地对象列表和对象注释列表。评估控制对象排序列表中包含的对象,并实例化新控件,将其属性设置为与存储的属性值匹配,并将适当的事件处理程序与新控件关联。一旦所有新控件都已定义并添加到面板控件中,树视图将更新。请注意,对象注释排序列表除了直接将其写入当前对象注释排序列表之外,不需要任何处理。这是因为整个类及其所有属性都是直接可序列化的。

下一段代码实现了主菜单中的保存功能

    Private Sub SaveToolStripMenuItem_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
            Handles SaveToolStripMenuItem.Click

        Try
            ' Open a file dialog for saving BXS documents
            SaveFileDialog1.Title = "Save BXS Document"
            SaveFileDialog1.Filter = "BXS Documents (*.bxs)|*.bxs"

            If SaveFileDialog1.ShowDialog() = 
            Windows.Forms.DialogResult.Cancel Then
                Exit Sub
            End If

        Catch ex As Exception

            Exit Sub

        End Try

        ' Exit if no BXS document is selected
        Dim sFilePath As String
        sFilePath = SaveFileDialog1.FileName
        If sFilePath = "" Then Exit Sub

        ' Create a new sorted list to hold the data contain in the
        ' user controls for subsequent serialization
        Dim ObjectData As New SortedList

        ' Move the notes and object collections into the file bundle
        ' for subsequetn serialization
        Dim de As DictionaryEntry
        For Each de In mObjectCollection
            Select Case de.Value.GetType.ToString()
                Case "ObjectManager.Animal"
                    Dim ad As New AnimalData
                    ad.theId = de.Key
                    ad.theColor = de.Value.MajorColor
                    ad.theDisplayName = de.Value.DisplayName
                    ad.theHabitat = de.Value.Habitat
                    ad.theSpecies = de.Value.Species
                    ad.theLocation = de.Value.Location
                    ObjectData.Add(ad.theId, ad)
                Case "ObjectManager.Person"
                    Dim ps As New PersonData
                    ps.theId = de.Key
                    ps.theBirthPlace = de.Value.BirthPlace
                    ps.theDateOfBirth = de.Value.DateOfBirth
                    ps.theDisplayName = de.Value.DisplayName
                    ps.theLocation = de.Value.Location
                    ps.theOccupation = de.Value.Occupation
                    ps.thePersonName = de.Value.PersonName
                    ObjectData.Add(ps.theId, ps)
                Case "ObjectManager.Place"
                    Dim pls As New PlaceData
                    pls.theId = de.Key
                    pls.theDisplayName = de.Value.DisplayName
                    pls.thePlaceName = de.Value.PlaceName
                    pls.theCity = de.Value.City
                    pls.theState = de.Value.State
                    pls.theCountry = de.Value.Country
                    pls.theLocation = de.Value.Location
                    ObjectData.Add(pls.theId, pls)
                Case Else
                    'do nothing
            End Select
        Next

        ' Place the object data and notes into a sorted list
        ' for subsequent serialization to a file location
        mObjectFileBundle = New SortedList
        mObjectFileBundle.Add("TheObjects", ObjectData)
        mObjectFileBundle.Add("TheNotes", mObjectNotes)

        ' Once the values have been set, serialize the content into the
        ' file path established using the save file dialog
        FileSerializer.Serialize(sFilePath, mObjectFileBundle)

    End Sub

此代码的工作方式类似于“打开文件”菜单选项,尽管它是反向操作。在这里,用户会看到一个“文件保存”对话框;该对话框用于收集有效的路径,用于将序列化的应用程序数据存储为自定义(`.bxs`)文件。一旦选择或创建了有效路径,子例程将从控件对象集合中收集控件对象数据,并将其移入单独的可序列化集合。为每种控件类型定义的结构用于包含每个控件对象属性的单独副本;不是所有属性,而是我们决定保留的那些属性。

一旦控制数据收集到对象数据集合中,对象注释和对象数据排序列表都将添加到对象文件捆绑排序列表(以便所有数据现在都存储在单个排序列表中)。然后,对象文件捆绑将序列化到指定的文件路径。

接下来的两个代码段用于处理通过在文件资源管理器窗口中双击自定义文件类型的关联文件来打开应用程序。第一个子例程将窗体加载配置为查找在应用程序加载时传递给应用程序的文件路径字符串,如果传递了任何文件路径,则将其路径传递给第二个子例程,该子例程在启动时将其加载到应用程序中。该代码如下所示

    Private Sub frmMain_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.Load

        'check each parameter to get the file name (there is only one though)
        For Each param As String In My.Application.CommandLineArgs

            Try
                ' pass the file path if it exists
                OpenFromPath(param)
            Catch
                'do nothing, just open the application with no file
            End Try

        Next param

    End Sub

    Private Sub OpenFromPath(ByVal sFilePath As String)

        mObjectFileBundle = New SortedList
        mObjectFileBundle = FileSerializer.Deserialize(sFilePath)

        Dim ObjectData As New SortedList

        ' Recover the object collections to populate the local
        ' sortedlists used to hold the objects.
        Dim de As DictionaryEntry
        For Each de In mObjectFileBundle
            If de.Key.ToString() = "TheObjects" Then
                ObjectData = de.Value
            ElseIf de.Key.ToString() = "TheNotes" Then
                mObjectNotes.Clear()
                mObjectNotes = de.Value
            End If

        Next

        ' Add the objects back into the panel by recovering the stored
        ' object data and creating a new set of controls with the properties
        ' set to the object data gathered from the original object during
        ' file save and serialization
        Dim de2 As DictionaryEntry
        For Each de2 In ObjectData
            Select Case de2.Value.GetType.ToString()
                Case "ObjectManager.frmMain+AnimalData"
                    Dim ast As New AnimalData
                    ast = de2.Value
                    Dim animl As New Animal(ast.theId, _
                                            ast.theDisplayName, _
                                            ast.theSpecies, _
                                            ast.theType, _
                                            ast.theHabitat, _
                                            ast.theColor, _
                                            ast.theLocation)
                    animl.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(animl.ID, animl)
                    AddHandler animl.MouseClick, AddressOf Object_MouseClick
                    AddHandler animl.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(animl)
                Case "ObjectManager.frmMain+PersonData"
                    Dim pst As New PersonData
                    pst = de2.Value
                    Dim pers As New Person(pst.theId, _
                                           pst.theDisplayName, _
                                           pst.thePersonName, _
                                           pst.theBirthPlace, _
                                           pst.theDateOfBirth, _
                                           pst.theOccupation, _
                                           pst.theLocation)
                    pers.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(pers.ID, pers)
                    AddHandler pers.MouseClick, AddressOf Object_MouseClick
                    AddHandler pers.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(pers)
                Case "ObjectManager.frmMain+PlaceData"
                    Dim pls As New PlaceData
                    pls = de2.Value
                    Dim plc As New Place(pls.theId, _
                                         pls.theDisplayName, _
                                         pls.thePlaceName, _
                                         pls.theCity, _
                                         pls.theState, _
                                         pls.theCountry, _
                                         pls.theLocation)
                    plc.ContextMenuStrip = ContextMenuStrip1
                    mObjectCollection.Add(plc.ID, plc)
                    AddHandler plc.MouseClick, AddressOf Object_MouseClick
                    AddHandler plc.MouseDown, AddressOf Object_MouseDown
                    Panel1.Controls.Add(plc)
                Case Else
                    ' do nothing
            End Select
        Next

        ' update the treeview to show the objects loaded from a file
        UpdateTreeview()

    End Sub

“从文件打开”子例程的代码与“文件打开”菜单单击事件处理程序中使用的一部分代码相同,不同之处在于它直接处理传递给函数的 文件路径,而不是通过从“打开文件”对话框收集的值。此函数可以直接从“文件打开”菜单事件处理程序调用,而不会对应用程序产生任何影响。

应用程序中的最后一个子例程仅用于响应用户从主菜单中选择退出功能而关闭应用程序

Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click

        Application.Exit()
End Sub

至此,主窗体代码中包含的代码以及所有应用程序代码的描述已完成。

代码:文件序列化程序模块

代码模块包含两种方法;一种用于序列化数据,另一种用于反序列化数据。两种方法都使用二进制格式化程序来处理序列化和反序列化过程。查看模块并注意每种方法的构造方式。

`Serialize` 子例程如下所示

Sub Serialize(ByVal strPath As String, ByVal myFile As SortedList)

        ' Create a filestream to allow saving the file after it has 
        ' been serialized in this method
        Dim fs As New FileStream(strPath, FileMode.OpenOrCreate)

        ' Create a new instance of the binary formatter
        Dim formatter As New BinaryFormatter
        'Dim formatter As New SoapFormatter

        Try

            ' save the serialized data to the file path specified
            formatter.Serialize(fs, myFile)
            fs.Close()

        Catch ex As SerializationException

            MessageBox.Show(ex.Message & ": " & ex.StackTrace, "Error", 
            MessageBoxButtons.OK, MessageBoxIcon.Error)

        End Try

End Sub

该子例程非常简单;它打开一个文件流,指向参数列表中传递的文件路径字符串;文件模式设置为 `Open` 或 `Create`。创建文件流后,创建 `BinaryFormatter` 的新实例。然后设置 `Try Catch` 块;`Try` 部分用于调用格式化程序的 `Serialize` 方法;此方法接受两个参数:一个用于标识要写入的文件流,一个用于接受文件,在这种情况下,该文件是包含应用程序对象和注释对象引用的排序列表。序列化程序完成向文件流写入后,文件流关闭。

文件序列化程序类中的另一种方法用于将存储文件的内容反序列化回应用程序的文件捆绑排序列表。从文件重建后,排序列表用于重新创建工作区对象。该代码如下所示

    Public Function Deserialize(ByVal strPath As String) As SortedList

        ' Create filestream allowing the user to open an existing file
        Dim fs As New FileStream(strPath, FileMode.Open)

        ' Create a new instance of the Personal Data class
        Dim myFile As SortedList
        myfile = New SortedList

        Try

            ' Create a binary formatter
            Dim formatter As New BinaryFormatter
            'Dim formatter As New SoapFormatter

            ' Deserialize the data stored in the specified file and 
            ' use that data to populate the new instance of the personal data 
              class.
            myfile = formatter.Deserialize(fs)
            fs.Close()

            ' Return the deserialized data back to the calling application
            Return myFile

        Catch ex As SerializationException

            MessageBox.Show(ex.StackTrace, ex.Message, MessageBoxButtons.OK, 
            MessageBoxIcon.Error)
            Return myFile

        End Try

    End Function

此代码的工作方式与序列化程序子例程非常相似,因为它打开文件流并返回文件的内容(文件捆绑排序列表),该内容传递给名为“`myFile`”的新排序列表。反序列化程序函数将重建的排序列表返回给调用方法。在这种情况下,调用方法是主窗体中的文件打开命令;文件打开命令然后从文件捆绑排序列表重建对象和注释排序列表。

自定义文件关联

如果您想创建自定义文件关联,以便当用户在 Windows 资源管理器窗口中双击自定义类型扩展名的文件时,应用程序将直接打开它;您将需要在设置和部署包中设置该关联,并且您需要修改应用程序的主入口点,以允许它在初始化后立即处理传递给它的文件。

通过使用 Visual Studio 2005 设置和部署项目创建文件类型关联比以前更容易管理;您可以手动将必要信息编码到应用程序中,并且根据您使用的安装包,您可能需要这样做。如果您对此方法感兴趣,请查看此链接,[请参阅“困难地”创建文件关联]。

首先,在应用程序的主窗体中,您需要修改窗体加载事件处理程序以响应命令行参数(文件名)的接收。这现在是一段微不足道的代码,它应该看起来像这样

Private Sub frmMain_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load

        'check each parameter to get the file name (there is only one though)
        For Each param As String In My.Application.CommandLineArgs

            Try
                ' pass the file path if it exists
                OpenFromPath(param)
            Catch
                'do nothing, just open the application with no file
            End Try

        Next param

     End Sub

在此示例中,当窗体加载时,它将检查 `My.Application.CommandLineArgs` 的内容以查看它是否包含文件名;由于这是我们将传递的所有内容,它将为空或包含文件路径。如果存在文件路径,它将传递给一个名为“`OpenFromPath`”的子例程,该子例程从文件中捕获数据,然后重建文件的对象。

为了将文件路径传递给命令行参数,您需要在“设置和部署项目”中设置一些东西。首先,向现有解决方案添加一个“设置和部署项目”,并将其配置为满足您的要求。添加项目后,在“解决方案资源管理器”中单击设置项目的名称,单击“视图”,然后单击“文件类型”选项。这将会在 Visual Studio 的主窗口中显示一个“文件类型设计器”。

显示文件类型设计器后,右键单击“目标计算机上的文件类型”,然后单击“添加文件类型”选项。这将在树视图中添加一个空文件类型,选择新文件类型的节点,并在属性编辑器中查看

图 5:文件类型属性编辑器

在属性编辑器中,将名称设置为与自定义类型名称匹配,将命令指向应用程序(作为应用程序的主输出),输入描述,将扩展名设置为自定义文件类型的扩展名,并为文件类型设置图标。设置这些值后,单击“打开”节点。

图 6:自定义文件类型下的“打开”节点

单击“打开”节点后,属性编辑器将显示这些属性

图 7:属性编辑器设置为“打开”节点

这些默认属性是正确的,因为默认进程设置为“打开”,并且“%1”设置为在启动时将所选文件的文件名传递给应用程序的命令行参数。用户安装应用程序后,当他们双击自定义文件类型的文件时,它会将文件路径传递给应用程序并打开它。此外,如果用户右键单击自定义文件类型图标,他们将能够选择“打开方式”选项,并显示指向您应用程序的链接,如下图所示

图 8:Windows 资源管理器上下文菜单中的“打开方式”选项

摘要

本文的目的是演示一种简单的方法,为以在运行时创建和编辑对象为中心的应用程序提供完整的框架。为了实现该目标,该应用程序演示了以下关键元素

  • 拖放以支持应用程序工具箱界面
  • 面板拖放以支持在运行时移动现有对象
  • 树视图控件用于提供所有现有父对象和子对象的映射
  • 树视图控件用于允许对象选择
  • 树视图控件用于通过简单递归删除项目对象和子对象
  • 属性网格控件用于编辑对象属性
  • 序列化用于存储包含重建文件之间所需所有必要数据的文件(通过自定义文件类型实现集体对象持久化)
  • 反序列化用于恢复存储的、序列化的对象以支持文件打开功能,并随后用于将存储的工作区恢复到活动应用程序
  • 处理命令行参数以在初始化后立即打开文件
  • 通过安装和部署包创建自定义文件类型
构建您自己的 Visual Studio:一个用于在运行时编辑对象的应用程序框架 - CodeProject - 代码之家
© . All rights reserved.