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

使用面板窗体、多闪屏、SQL Express 等在 Windows 应用程序开发中的指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (43投票s)

2005 年 9 月 19 日

CPOL

16分钟阅读

viewsIcon

111488

downloadIcon

2462

一个快速的实践应用程序,指导您使用面板窗体、多闪屏、SQL Express 等...

引言

在撰写了文章《在 Windows 应用程序中使用面板窗体》之后,我决定在 VS 2005 Beta 2 中做些改进。尽管我最初的意图是写一个简单的应用程序来探索 Beta 2 中面板窗体开发的“如何做”,但随着我越来越深入地研究这个工作室,我发现自己往应用程序里添加了越来越多的东西。最后,我用 VB.NET 和 SQL Express 作为后端,构建了一个简单的地址簿,其中使用了面板窗体。

面板窗体

我想我先从面板窗体开始,解释一下如何在 Visual Studio 2005 中实现它们。正如我在上一篇文章《在 Windows 应用程序中使用面板窗体》中所解释的那样,我们可以使用一个带有面板控件的单个窗体,该面板控件可以用于加载用户控件。用户控件提供了什么?除了无法充当窗体之外,一切都可以。也就是说,窗体是独立的,而用户控件是依赖的。用户控件是一种类型,而窗体是 Windows 应用程序的一个成员。(创建一个项目,然后删除任何窗体,尝试将用户控件创建为启动项,会返回以下错误:“UserControl1”是“TestApp”中的一个类型,不能用作表达式。)在这里提及用户控件几乎具有窗体的所有属性是很重要的……例如,我们可以为每个用户控件添加菜单,设置透明度,自动调整控件大小等。

在此,我想请求那些还没有阅读过文章《在 Windows 应用程序中使用面板窗体》的读者,在继续阅读之前先阅读那篇文章,因为我不会在这里详细解释整个过程。

与我之前的应用程序一样,这个应用程序有一个 Windows 窗体“myMainForm”和各种用户控件。这个单一窗体包含一个名为“myPanel”的面板,该面板将用于填充用户控件。如图 1 所示,窗体上灰色的矩形区域就是面板。在该窗体的 Load 事件中,我希望将一个用户控件添加到面板中进行显示。以下是相关的代码:

    Dim tempObject As New ctrlMain
    Me.myPanel.Controls.Clear()
    Me.myPanel.Controls.Add(tempObject)

我已将这些行包含在 myMainForm_Load(…,…) 函数中。因此,当窗体加载时,应用程序会创建一个用户控件 ctrlMain(图 2)的对象,并通过调用 Add 方法并传递创建的对象,将其添加到 myPanel 中。有人可能会问,第二条语句,即 myPanel.Controls.Clear() 的意义是什么。它会清除面板,移除所有已有的控件。我为什么在这里调用它?没有特别的原因。注释掉这行代码,应用程序的运行方式与之前相同。尽管我在代码中包含了它,但原因是为了让您熟悉 Clear 方法,因为稍后我将解释如何使用这种常见的方法会导致执行错误……

现在,我们希望转移到另一个控件,以我们应用程序为例,假设我们点击标记为“Add New Entry”的按钮,这就需要将当前控件与 ctrlAddNewEntry(图 3)进行交换。以下是用于完成此任务的三行代码:

Dim tempObject As New ctrlAddNewEntry
Me.ParentForm.Controls.Item("myPanel").Controls.Add(tempObject)
Me.ParentForm.Controls.Item("myPanel").Controls.RemoveAt(0)

在第一行中,我们创建了控件 ctrlAddNewEntry 的一个对象。在下一行中,我们将该对象添加到 myPanel 的控件集合中。

棘手之处在于,我们不是在我们的主窗体 myMainForm 中操作。我们必须在当前的用​​户控件内部访问我们的面板。有两种方法可以做到这一点。第一种方法是使用 Parent 属性来访问主窗体。另一种方法是使用 ParentForm 属性,如上面所示。我更喜欢 ParentForm 属性而不是 Parent 属性。它要简单得多,因为 ParentForm 属性会访问控件的父窗体,而 Parent 属性会访问控件的父容器。

另外请注意 VS 2003 和 VS 2005 中面板窗体的主要区别,我们是通过名称访问面板的,如果您还记得我之前的文章,我们就必须使用设计器生成的代码中的索引来访问它,而没有名称。这无疑非常方便,因为在这种情况下不会出现获取错误索引的可能性!!!

第三行代码从面板的控件集合中移除索引为 0 的控件。控件集合充当队列,遵循 FIFO(先进先出)规则。因此,当我们向面板的控件集合添加一个新控件时,它会存储在索引 1 处。移除索引为 0 的控件后,新控件将从索引 1 移到索引 0。

警告!!! 在实践中,编码人员会尝试使用 myPanel.Controls.Clear() 来清除面板中的所有控件,然后添加新控件。同样,有些人会在向面板添加新控件之前调用 myPanel.Controls.RemoveAt(0)。这两种方法都会导致以下错误:“未将对象引用设置到对象的实例”。这是因为该命令会清除控件集合中的最后一个项目,并将其处置掉。尽管面板仍然可以访问,但访问控件集合将导致错误。

现在,如果我们想再次交换我们的用户控件,我们可以使用相同的三个代码行,唯一的变化是我们将创建要交换到的控件的对象。

多闪屏

现在我们已经介绍了 VS 2005 中面板窗体的实现方法,让我们开始介绍闪屏。闪屏通常用于在应用程序加载时向用户显示信息。它们基本上是 Windows 窗体,但没有窗体边框。Visual Basic 提供了一个预配置的闪屏窗体模板,可以添加到 Windows 应用程序项目中,并在项目设计器中提供一个闪屏属性,允许为项目指定闪屏。


创建闪屏

要创建闪屏,只需将一个 Windows 窗体添加到项目中。在设计器模式下打开窗体,右键单击它并打开其属性。向下滚动到窗体的“外观”属性,并将 FormBorderStyle 属性设置为“无”。接下来,向下滚动到“窗口样式”属性,并将 ShowInTaskbar 属性设置为 false

现在选择一个您想用作闪屏的图像,并将其添加为窗体的背景图像。我通常会在我的闪屏上添加两个标签,它们如下:

1. 应用程序标题

此标签将在闪屏上显示应用程序标题。我们可以在闪屏的 Load 事件中设置其值。最好的方法是使用以下方式从程序集信息中获取应用程序标题:

    My.Application.Info.Title

但是,应用程序标题很有可能为空,除非我们提前指定。为了解决这个问题,我们可以使用以下检查:

    My.Application.Info.Title <> ""

如果为 true,则使用此值,否则我们可以通过调用以下方法获取不带扩展名的应用程序文件名:

    System.IO.Path.GetFileNameWithoutExtension()
    Parameter: My.Application.Info.AssemblyName

2. 版本

用户可能想在闪屏上看到的另一件事是版本信息。我使用的是默认闪屏模板中指定的 String.Format() 函数。我不会在这里解释这个函数,但会建议使用 MSDN 帮助上的 String.Format() 以获得更好的理解。

接下来要做的是让闪屏在任何其他窗体之前显示。最快最简单的方法是使用项目设计器的闪屏属性(图 4)。但问题在于,很难控制闪屏打开多长时间、它何时失去焦点、它如何出现以及如何为其添加附加功能。


我的闪屏

我将直接跳到我正在使用的闪屏以及我如何使用它。闪屏如图 5 所示。由于我的应用程序的启动窗体是 myMainForm,因此我在其 OnLoad 事件中添加了以下代码:

    'Splash Screen Call – Version 0.02
    mySplashScreen.ShowDialog()

感到困惑?我知道。我先是使用了以下语法:

    'Splash Screen Call – Version 0.01
    Dim tempForm As New mySplashScreen
    tempForm.ShowDialog()

为什么我改变了它?为什么在后续版本中我没有创建 mySplashScreen 的对象?我会回答这些问题,但首先我需要解释闪屏的功能。暂时认为我使用的是闪屏版本 0.01。

让我们将以下文本添加到 mySplahScreenonLoad 事件调用中:

    If My.Application.Info.Title <> "" Then
        ApplicationTitle.Text = My.Application.Info.Title
    Else
        ApplicationTitle.Text = 
            System.IO.Path.GetFileNameWithoutExtension(_
                        My.Application.Info.AssemblyName)
    End If

    Version.Text = System.String.Format(Version.Text, _
                    My.Application.Info.Version.Major, _
                    My.Application.Info.Version.Minor)
    
    Copyright.Text = My.Application.Info.Copyright

正如您所猜到的,这是来自默认闪屏模板的代码。它的作用是为闪屏上的标签设置值。

接下来,向闪屏添加一个计时器控件。我们将在 mySplahScreen 的相同 onLoad 事件调用中初始化此控件。我们将计时器控件命名为 'timerSplash'。我还声明了以下共享变量:

    Public Shared opacityRate As Double = 0.0
    Public Shared maximizeRate As Boolean = True
    Public Shared minimizeRate As Boolean = False
    Public Shared killApplication As Boolean = False

现在,让我们在 OnLoad 事件过程中添加代码,就在设置版权文本之后:

    Me.Opacity = 0.0
    timerSplash.Interval = 50
    timerSplash.Enabled = True
    timerSplash.Start()

在第一行中,我将窗体的透明度,即闪屏的透明度设置为零……因此在初始阶段它将不可见。然后我将 timerSplash 的间隔设置为 50,使其 enabled,然后告诉它开始执行。

至此,闪屏的 onLoad 过程结束。剩下的就是计时器滴答事件处理程序,即处理 timerSplash.Tick 事件的函数。

timerSplash_Tick 函数将在每个间隔后被调用。1000 的间隔等于 1 秒。我们设置的间隔是该秒的一小部分。让我们添加此事件处理程序的代码:

If opacityRate >= 1.0 Then
    opacityRate = opacityRate + 1.0
    If opacityRate >= 20.0 Then
        'Try
            'Dim tempBoolean As Boolean = DataLayer.Open()
            'If tempBoolean = False Then
                'killApplication = True
            'End If
        'Catch ex As Exception
            'killApplication = True
    'End Try

        opacityRate = 0.99
        Me.Opacity = opacityRate
    End If
ElseIf maximizeRate Then
    opacityRate = opacityRate + 0.025
    Me.Opacity = opacityRate
    If opacityRate >= 1.0 Then
        maximizeRate = False
        minimizeRate = True
    End If
ElseIf minimizeRate Then
    opacityRate = opacityRate - 0.025
    If opacityRate < 0 Then
        opacityRate = 0
    End If
    Me.Opacity = opacityRate
    If Opacity <= 0.0 Then
        minimizeRate = False
        maximizeRate = False
    End If
Else
    timerSplash.Stop()
    timerSplash.Enabled = False
    timerSplash.Dispose()
    Me.Close()
End If

上面的代码的作用是:当调用闪屏时,它会显现出来,保持一秒钟的实体状态,然后再次隐去。在初始化期间,我设置了四个变量。其中三个;opacityRateminimizeRatemaximizeRate,在此 splashTimer_Tick 事件处理程序中使用。

默认情况下,在初始化时,minimizeRate 设置为 falsemaximizeRate 设置为 trueopacityRate 设置为 0.0。此外,窗体/闪屏的透明度属性也设置为零。

人们可以轻松理解 splashTimer_Tick 函数是如何工作的,所以我不会深入细节。这里重要的是要注意:在前 10 次滴答后,即前 0.5 秒,窗体达到最大透明度,在接下来的 20 次滴答(1 秒)后,minimizeRate 设置为 true,这样窗体将在接下来的 10 次滴答(0.5 秒)中失去透明度。因此,闪屏的总显示时间为 2 秒。

起初这对我来说很棒,我使用了闪屏调用版本 0.01,闪屏出现在启动窗体之前,当它关闭时,主应用程序窗体出现。然后我决定在 myMainForm 中添加一个测试。我想做的是检查 SQL Express 是否存在以及我是否可以连接到它。

在我的 DataLayer.vb 文件中(当我讲到 SQL Express 时会详细讨论),我创建了一个名为 Open 的函数,如果成功建立数据库连接,则返回 true,否则返回 false

在闪屏调用版本 0.01 之后,我在 myMainForm_OnLoad 事件中添加了以下代码:

    Dim tempBoolean As Boolean = DataLayer.Open()
    If tempBoolean = False Then
        Dim tempForm As New myMessageBox
        tempForm.Text = "Error!"
        tempForm.lblHeader.Text = "Database Communication Error!!!"
        Dim temp(2) As String
        temp(0) = "An error occurred while communicating with 
                   database. Please check if SQL Express is 
                   Running or that it exists."
        temp(1) = ""
        temp(2) = "Application will now exit."
        tempForm.txtMessage.Lines = temp
        tempForm.ShowDialog()
        Me.Close()
    End If

结果正如预期的那样,在显示闪屏后,应用程序检查数据库连接性,如果失败则显示一个消息框然后退出。重要的是要注意,我使用的是自定义消息框 myMessageBox,如图 6 所示。另一个需要注意的重要事项是,在调用 showDialog 函数时,执行会转移到正在显示的窗体/对话框,因此 Me.Close 仅在消息框关闭时才会被调用。

在最好的情况下,当数据库连接建立时,这工作得非常好,但当无法连接到数据库时,它会花费很长时间。因此,在闪屏关闭后,在显示 myMessageBoxmyMainForm 之前会有一个很长的间隔。

然后我将数据库连接检查从 myMainForm_OnLoad 事件转移到 myMainForm_OnShown 事件……至少在闪屏关闭后,主应用程序窗体就会显示出来。但是,当应用程序检查数据库连接时,myMainForm 会挂起执行,如果连接无法建立,会发生长时间的延迟。

最后,我决定将代码添加到闪屏中。我在 splashTimer_Tick 函数中添加了代码,当闪屏达到完全透明度时。唯一需要处理的是,我们不能从闪屏调用 Application.Exit(),因为这会产生错误(在调用闪屏时 myMainForm 尚未完全初始化)。因此,我创建了另一个共享变量,第四个变量 killApplication

splashTimer_Tick 事件中,如果您取消注释被注释掉的代码,它将处理连接性检查。它所做的工作很简单,默认情况下 killApplication 初始化为 false,但是如果连接失败,需要做的就是将 killApplication 设置为 false

然后我对 myMainForm_OnLoad 事件进行了一些更改。我想以某种方式获取 killApplication 的值,以便知道连接是失败还是成功。使用闪屏调用版本 0.01,我发现我们无法从另一个窗体访问闪屏的共享变量。然后我尝试使用闪屏调用版本 0.02,结果是我可以访问那些变量。这是由于以下原因:如果创建了一个窗体的对象,共享变量只能被该对象访问,并且只属于该对象;但如果一个窗体被直接调用,其共享变量是全局可访问的。

因此,最终我有以下两个函数:

Private Sub myMainForm_Load(...,...) Handles MyBase.Load
    mySplashScreen.ShowDialog()

    Dim tempObject As New ctrlMain
    Me.myPanel.Controls.Clear()
    Me.myPanel.Controls.Add(tempObject)
End Sub

Private Sub myMainForm_Shown(...,...) Handles MyBase.Shown
    If mySplashScreen.killApplication Then
        Dim tempForm As New myMessageBox
        tempForm.Text = "Error!"
        tempForm.lblHeader.Text = "Database Communication Error!!!"
        Dim temp(2) As String
        temp(0) = "An error occurred while communicating with 
                   database. Please check if SQL Express is 
                   Running or that it exists."
        temp(1) = ""
        temp(2) = "Application will now exit."
        tempForm.txtMessage.Lines = temp
        tempForm.ShowDialog()

        Me.Close()
    End IfIf
End Sub

在这样做了并实现了我想要的结果之后,我决定添加另一个闪屏,一个将在用户退出应用程序时显示的屏幕,我的结束屏幕 endScreen,如图 7 所示。

Private Sub endScreen_Load(..., ...) Handles MyBase.Load
    timerExit.Interval = 1500
    timerExit.Enabled = True
    timerExit.Start()
End Sub

Private Sub timerExit_Tick(..., ...) Handles timerExit.Tick
    timerExit.Stop()
    timerExit.Enabled = False
    timerExit.Dispose()
    Me.Close()
End Sub

与之前的闪屏 mySplashScreen 一样,我也在其中包含了一个计时器。在 Load 事件中,我将计时器的间隔设置为 1500(1.5 秒),使其 enabled,并开始执行。在计时器滴答时,我停止计时器,禁用它,处置它,然后关闭闪屏。

我从 myMainFormFormClosing 事件中调用闪屏。以下是其代码:

Private Sub myMainForm_FormClosing(..., ...) Handles MyBase.FormClosing
    Me.Hide()
    endScreen.ShowDialog()
    Try
        DataLayer.Close()
    Catch ex As Exception
    End Try
End Sub

如前所述,使用 ShowDialog() 而不是 Show() 调用 endScreen 可以避免在 FormClosing 中暂停执行。

我忘记提到的一件事是,在 mySplashScreen 中建立的连接是整个应用程序使用的连接,并在退出时关闭它。连接在 myMainFormFormClosing 事件中关闭。

重要提示:如果您希望您的闪屏能够“绕着边缘工作”,即窗体在图像结束的地方结束,即使它不是矩形的,那么请将您的闪屏图像设置为 .PNG.GIF 格式。看图 5,这是我使用的闪屏,如果我点击图像边缘之外的某个地方,我就会点击桌面。同样,看图 10;如果我点击不在图像上的任何地方,即使是绿色边框之间的区域,我也会点击桌面。我将在 resources 文件夹中包含闪屏的 .PNG 图像;您可以更改 mySplashScreen 的背景图像,并将其设置为图 10 中显示的图像,以了解其工作原理。

但请记住,此功能仅适用于 Visual Studio 2005,在 Visual Studio 2003 或更早版本中无效。

SQL Express

为应用程序添加 SQL Express 数据库就像添加 Windows 窗体一样简单。只需在解决方案资源管理器中右键单击您的项目,然后选择“添加”>“新项”。从列出的 Visual Studio 已安装模板中选择 SQL 数据库,如果您安装了 SQL Express,它应该会出现在那里。

单击“添加”。数据库被创建,并在解决方案资源管理器中出现一个图标。一个新的窗口弹出,这是默认的数据集生成器。关闭它,因为我们不会使用它。双击解决方案资源管理器中的数据库,数据库将被加载到服务器资源管理器中。要添加新表,请右键单击“表”文件夹并选择“添加新表”,要添加新存储过程,请右键单击“存储过程”并选择“添加新存储过程”。

我不会详细介绍定义表和存储过程的“如何做”,但我希望您花些时间探索 SQL Express,特别是查询生成器,它在构建复杂查询方面非常方便。

注意:一个应用程序可以包含多个 SQL Express 数据库。

让我们开始处理 DataLayer.vb,这是包含所有函数和数据库调用的类。

我声明了两个共享变量;

Public Shared dataCommand As New Data.SqlClient.SqlCommand
Public Shared sqlConnection1 As New Data.SqlClient.SqlConnection(...)

Data.SqlClient.SqlConnection 的参数是连接字符串。您可以创建自己的连接字符串,或者使用自动生成的连接字符串。在服务器资源管理器中单击数据库,属性选项卡将显示数据库的属性(单击解决方案资源管理器中的数据库也会显示属性,但并非全部,特别是连接字符串)。通过从属性选项卡复制连接字符串并将其粘贴到 DataLayer 中来使用它。

接下来,我们需要打开到数据库的连接:

Public Shared Function Open() As Boolean
    Try
        dataCommand.CommandType = Data.CommandType.StoredProcedure
        dataCommand.Connection = sqlConnection1
        sqlConnection1.Open()
        If sqlConnection1.State = Data.ConnectionState.Open Then
            Return True
        Else
            Return False
        End If
    Catch ex As Exception
        Debug.Write("While Opening the connection to the DB: " + _
                                                          ex.Message)
    Return False
    End Try
End Function

就这样……接下来,让我们看看如何使用存储过程从数据库中获取数据:

Public Shared Function selectDetails(ByVal id As String) As _
                                    Data.SqlClient.SqlDataReader
    Dim reader As Data.SqlClient.SqlDataReader
    Try
        dataCommand.CommandText = "selectDetails"
        dataCommand.Parameters.Clear()
        dataCommand.Parameters.Add("@id", Data.SqlDbType.VarChar)
        dataCommand.Parameters("@id").Value = id
        reader = dataCommand.ExecuteReader()
        Return reader
    Catch ex As Exception
        Debug.Write(ex.Message)
        Return Nothing
    End Try
End Function

上面的函数接受一个参数 ID,调用数据库中的存储过程 'selectDetails',并将 ID 作为其参数传递。

这里需要注意的一个重要事项是,由于我使用的是共享的 dataCommand 对象,因此在每个函数中调用 dataCommand.Parameters.Clear() 很重要,因为参数数量不正确会导致很多错误。

上一个函数使用 SqlDataReader 从数据库读取数据……如果我们不想这样做呢?另一种选择是使用 DataSet。以下函数调用一个存储过程并将结果存储在 DataSet 中。

Public Shared Function selectAllRecord() As Data.DataSet
    Dim ds As New Data.DataSet
    Try
        Dim adapter As New Data.SqlClient.SqlDataAdapter
        adapter.SelectCommand = New Data.SqlClient.SqlCommand(_
                                "selectAllRecord", sqlConnection1)
        adapter.Fill(ds, "tblAddressBook")
        Return ds
    Catch ex As Exception
        Debug.Write(ex.Message)
        Return Nothing
    End Try
End Function

同样,我们可以使用 dataCommand.executeNonQuery() 来执行非查询(更新、插入等)。

此外,我们可以在 SQL Express 中使用视图、函数等来增强我们的应用程序。我肯定会研究的一件事是,在一个应用程序中使用多个数据库,当它们之间需要交叉引用数据时。谁知道呢……也许那将是我的下一篇文章……

其他内容!!!

这篇文章的主要重点是面板窗体、闪屏和 SQL Express,但除此之外,当您下载应用程序/代码时,您还会发现 TreeView 控件(图 8 和 9)、Crystal Reports 以及丰富多彩的对话框得到了非常广泛的应用。

一切都必须结束……

希望您发现这篇文章有趣且信息丰富。正如我在上一篇文章中所说,我欢迎任何建议和意见,无论是负面的还是正面的。随时通过 msk.psycho@gmail.com 与我联系。

希望我很快能带着另一篇文章回来。在此之前……再见。

© . All rights reserved.