# **WinForm** WinForm 是 Windows Form 的简称,是基于 .NET Framework 平台的客户端(PC 软件)开发技术,一般使用 C# 编程。C# WinForm 编程需要创建「Windows 窗体应用程序项目。 .NET 提供了大量 Windows 风格的控件和事件,我们可以直接拿来使用,上手简单,开发快速。 ## **1.C#创建 Windows 窗体应用程序(WinForm 程序)** ## **2.C#设置窗体属性** 每一个 Windows 窗体应用程序都是由若干个窗体构成的,窗体中的属性主要用于设置窗体的外观。 在 Windows 窗体应用程序中右击窗体,在弹出的右键菜单中 选择“属性”命令,弹出如下图所示的属性面板。 ![属性](../images/属性.gif "属性") 在该图中列出的属性分为布局、窗口样式等方面,合理地设置好窗体的属性对窗体的 展现效果会起到事半功倍的作用。 窗体的常用属性如下表所示。 | 属性 | 作用 | | --------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Name | 用来获取或设置窗体的名称 | | WindowState | 获取或设置窗体的窗口状态,取值有 3 种,即 Normal(正常)、Minimized(最小化)、Maximized(最大化),默认为 Normal,即正常显示 | | StartPosition | 获取或设置窗体运行时的起始位置,取值有 5 种,即 Manual(窗体位置由 Location 属性决定)、CenterScreen(屏幕居中)、WindowsDefaultLocation( Windows 默认位置)、WindowsDefaultBounds(Windows 默认位置,边界由 Windows 决定)、CenterParent(在父窗体中居中),默认为 WindowsDefaultLocation | | Text | 获取或设置窗口标题栏中的文字 | | MaximizeBox | 获取或设置窗体标题栏右上角是否有最大化按钮,默认为 True | | MinimizeBox | 获取或设置窗体标题栏右上角是否有最小化按钮,默认为 True | | BackColor | 获取或设置窗体的背景色 | | BackgroundImage | 获取或设置窗体的背景图像 | | BackgroundImageLayout | 获取或设置图像布局,取值有 5 种,即 None(图片居左显示)、Tile(图像重复,默认值)、Stretch(拉伸)、Center(居中)、Zoom(按比例放大到合适大小) | | Enabled | 获取或设置窗体是否可用 | | Font | 获取或设置窗体上文字的字体 | | ForeColor | 获取或设置窗体上文字的颜色 | | Icon | 获取或设置窗体上显示的图标 | ## **C#添加窗体事件** 在窗体中除了可以通过设置属性改变外观外,还提供了事件来方便窗体的操作。 在打开操作系统后,单击鼠标或者敲击键盘都可以在操作系统中完成不同的任务,例如双击鼠标打开“我的电脑”、在桌面上右击会出现右键菜单、单击一个文件夹后按 F2 键可以更改文件夹的名称等。 实际上这些操作都是 Windows 操作系统中的事件。 在 Windows 窗体应用程序中系统已经自定义了一些事件,在窗体属性面板中单击闪电图标即可查看到窗体中的事件,如下图所示。 ![事件](../images/事件.gif "事件") 窗体中常用的事件如下表所示。 | 事件 | 作用 | | ---------------- | ---------------------------------------- | | Load | 窗体加载事件,在运行窗体时即可执行该事件 | | MouseClick | 鼠标单击事件 | | MouseDoubleClick | 鼠标双击事件 | | MouseMove | 鼠标移动事件 | | KeyDown | 键盘按下事件 | | KeyUp | 键盘释放事件 | | FormClosing | 窗体关闭事件,关闭窗体时发生 | | FormClosed | 窗体关闭事件,关闭窗体后发生 | ## **C#窗体方法** 自定义的窗体都继承自 System.Windows.Form 类,能使用 Form 类中已有的成员,包括属性、方法、事件等。 实际上窗体中也有一些从 System.Windows.Form 类继承的方法,如下表所示。 | 方法 | 作用 | | ------------------------- | ------------------------ | | void Show() | 显示窗体 | | void Hide() | 隐藏窗体 | | DialogResult ShowDialog() | 以对话框模式显示窗体 | | void CenterToParent() | 使窗体在父窗体边界内居中 | | void CenterToScreen() | 使窗体在当前屏幕上居中 | | void Activate() | 激活窗体并给予它焦点 | | void Close() | 关闭窗体 | ## **C# MessageBox:消息框** 消息框在 Windows 操作系统经常用到,例如在将某个文件或文件夹移动到回收站中时系统会自动弹出如下图所示的消息框。 ![MessageBox](../images/messageBox.gif) 在 Windows 窗体应用程序中向用户提示操作时也是采用消息框弹出的形式。 消息框是通过 McssageBox 类来实现的,在 MessageBox 类中仅定义了 Show 的多个重载方法,该方法的作用就是弹出一个消息框。 由于 Show 方法是一个静态的方法,因此调用该方法只需要使用 MessageBox.Show( 参数 )的形式即可弹出消息框。 消息框在显示时有不同的样式, 例如标题、图标、按钮等。 常用的 Show 方法参数如下表所示。 | 方法 | 说明 | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | DialogResult Show(string text) | 指定消息框中显示的文本(text) | | DialogResult Show(string text, string caption) | 指定消息框中显示的文本(text)以及消息框的标题(caption) | | DialogResult Show(string text, string caption, MessageBoxButtons buttons) | 指定消息框中显示的文本(text)、消息框的 标题(caption)以及消息框中显示的按钮 (buttons) | | DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) | 指定消息框中显示的文本(text)、消息框的 标题(caption )、消息框中显示的按钮 (buttons)以及消息框中显示的图标(icon) | MessageBoxButtons 枚举类型主要用于设置消息框中显示的按钮,具体的枚举值如下。 - OK:在消息框中显示“确定”按钮。 - OKCancel:在消息框中显示“确定”和“取消”按钮。 - AbortRetryIgnore:在消息框中显示“中止” “重试”和“忽略”按钮。 - YesNoCancel:在消息框中显示“是” “否”和“取消”按钮。 - YesNo:在消息框中显示“是”和“否”按钮。 - RetryCancel:在消息框中显示“重试”和“取消”按钮。 MessageBoxIcon 枚举类型主要用于设置消息框中显示的图标,具体的枚举值如下。 - None:在消息框中不显示任何图标。 - Hand、Stop、Error:在消息框中显示由一个红色背景的圆圈及其中的白色 X 组成 的图标。 - Question:在消息框中显示由圆圈和其中的一个问号组成的图标。 - Exclamation、Warning:在消息框中显示由一个黄色背景的三角形及其中的一个感叹号组成的图标。 - Asterisk、Information:在消息框中显示由一个圆圈及其中的小写字母 i 组成的图标。 调用 MessageBox 类中的 Show 方法将返回一个 DialogResult 类型的值。 DialogResult 也是一个枚举类型,是消息框的返回值,通过单击消息框中不同的按钮得到不同的消息框返回值。 DialogResult 枚举类型的具体值如下。 - None:消息框没有返回值,表明有消息框继续运行。 - OK:消息框的返回值是 0K (通常从标签为“确定”的按钮发送)。 - Cancel:消息框的返回值是 Cancel (通常从标签为“取消”的按钮发送)。 - Abort:消息框的返回值是 Abort (通常从标签为“中止”的按钮发送)。 - Retry:消息框的返回值是 Retry (通常从标签为“重试”的按钮发送)。 - Ignore:消息框的返回值是 Ignore (通常从标签为“忽略“的按钮发送)。 - Yes:消息框的返回值是 Yes (通常从标签为“是“的按钮发送)。 - No:消息框的返回值是 No (通常从标签为“否“的按钮发送)。 ## **C# Label 和 LinkLabel:标签控件** 在 Windows 窗体应用程序中,每个窗体都必不可少地会用到文本框和标签控件。 由于在窗体中无法直接编写文本,通常使用标签控件来显示文本。 在 Windows 窗体应用程序中,标签控件主要分为普通的标签 (Label) 和超链接形式的标签 (LinkLabel) 。 普通标签 (Label) 控件的常用属性如下表所示。 | 属性名 | 作用 | | --------- | ----------------------------------------------------------------------- | | Name | 标签对象的名称,区别不同标签唯一标志 | | Text | 标签对象上显示的文本 | | Font | 标签中显示文本的样式 | | ForeColor | 标签中显示文本的颜色 | | BackColor | 标签的背景颜色 | | Image | 标签中显示的图片 | | AutoSize | 标签的大小是否根据内容自动调整,True 为自动调整,False 为用户自定义大小 | | Size | 指定标签控件的大小 | | Visible | 标签是否可见,True 为可见,False 为不可见 | 普通标签控件 (Label) 中的事件与窗体的事件类似,常用的事件主要有鼠标单击事件、 鼠标双击事件、标签上文本改变的事件等。 与普通标签控件类似,超链接标签控件 (LinkLabel) 也具有相同的属性和事件。 超链接标签主要应用的事件是鼠标单击事件,通过单击标签完成不同的操作。 ## **C# TextBox:文本框控件** 文本框 (TextBox) 是在窗体中输入信息时最常用的控件,通过设置文本框属性可以实现多行文本框、密码框等。 在窗体上输入信息时使用最多的就是文本框。 除了前面一节介绍的控件属性以外,文本框还有一些不同的属性, 如下表所示。 | 属性名 | 作用 | | ------------ | ------------------------------------------------------------------------------------------------------------- | | Text | 文本框对象中显示的文本 | | MaxLength | 在文本框中最多输入的文本的字符个数 | | WordWrap | 文本框中的文本是否自动换行,如果是 True,则自动换行,如果是 False,则不能自动换行 | | PasswordChar | 将文本框中出现的字符使用指定的字符替换,通常会使用“\*”字符 | | Multiline | 指定文本框是否为多行文本框,如果为 True,则为多行文本框,如果 为 False,则为单行文本框 | | ReadOnly | 指定文本框中的文本是否可以更改,如果为 True,则不能更改,即只读文本框,如果为 False,则允许更改文本框中的文本 | | Lines | 指定文本框中文本的行数 | | ScrollBars | 指定文本框中是否有滚动条,如果为 True,则有滚动条,如果为 False, 则没有滚动条 | 文本框控件最常使用的事件是文本改变事件 (TextChange),即在文本框控件中的内容改变时触发该事件。 ## **C# Button:按钮控件** 按钮主要用于提交页面的内容,或者是确认某种操作等。 按钮包括普通的按钮 (Button)、单选按钮 (RadioButton),本节主要讲解按钮的应用,单选按钮将在下一节为大家讲解。 按钮常用的属性包括在按钮中显示的文字 (Text) 以及按钮外观设置的属性,最常用的事件是单击事件。 ![button](../images/button.gif "button") ## **C# RadioButton:单选按钮控件** 在 C# 语言中 RadioButton 是单选按钮控件,多个 RadioButton 控件可以为一组,这一组内的 RadioButton 控件只能有一个被选中。 ![RadioButton](../images/radiobutton.gif "RadioButton") ## **C# CheckBox:复选框控件** 复选框主要的属性是:Name、Text、Checked。 其中: - Name:表示这个组件的名称; - Text:表示这个组件的标题; - Checked:表示这个组件是否已经选中。 主要的事件就是 CheckedChanged 事件。 ![ChekcBox](../images/checkbox.gif "CheckBox") ## **C# ComboBox:组合框控件** 在 C# WinForm 开发中组合框(ComboBox)控件也称下拉列表框,用于选择所需的选项。 使用组合框可以有效地避免非法值的输入。 在组合框中也有一些经常使用的属性,如下表所示。 | 属性名 | 作用 | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | DropDownStyle | 获取或设置组合框的外观,如果值为 Simple,同时显示文本框和列表框,并且文本框可以编辑;如果值为 DropDown,则只显示文本框,通过鼠标或键盘的单击事件展开文本框,并且文本框可以编辑;如果值为 DropDownList,显示效果与 DropDown 值一样,但文本框不可编辑。默认情况下为 DropDown | | Items | 获取或设置组合框中的值 | | Text | 获取或设置组合框中显示的文本 | | MaxDropDownltems | 获取或设置组合框中最多显示的项数 | | Sorted | 指定是否对组合框列表中的项进行排序,如果值为 True,则排序, 如果值为 False,则不排序。默认情况下为 False | 在组合框中常用的事件是改变组合框中的值时发生的,即组合框中的选项改变事件 SelectedlndexChanged。 此外,在组合框中常用的方法与列表框类似,也是向组合框中添加项、从组合框中删除项。 ![ComboBox](../images/ComboBox.gif "ComboBox") ## **C# DateTimePicker:日期时间控件** 在 C# 语言中日期时间控件(DateTimePicker)在时间控件中的应用最多,主要用于在界面上显示当前的时间。 日期时间控件中常用的属性是设置其日期显示格式的 Format 属性。 Format 属性提供了 4 个属性值,如下所示。 - Short:短日期格式,例如 2017/3/1; - Long:长日期格式,例如 2017 年 3 月 1 日; - Time:仅显示时间,例如,22:00:01; - Custom:用户自定义的显示格式。 如果将 Format 属性设置为 Custom 值,则需要通过设置 CustomFormat 属性值来自定义显示日期时间的格式。 ## **#日历控件(MonthCalendar)** 在 C# 中日历控件(MonthCalendar)用于显示日期,通常是与文本框联用,将日期控件中选择的日期添加到文本框中。 下面通过实例来学习日历控件的应用。 ![日历](../images/日历.gif "日历") ## **C# OpenFileDialog 和 SaveFileDialog:打开文件与保存文件** 在 C# WinForm 开发中文件对话框(FileDialog)主要包括文件浏览对话框,以及用于查找、打开、保存文件的功能,与 Windows 中的文件对话框类似,下面通过实例来演示文件对话框的使用。 ![fileDialog](../images/fileDialog.gif "FileDialog") ## **C#异常与调试** ### **C# Exception:异常类** .NET Framework 类库中的所有异常都派生于 Exception 类,异常包括系统异常和应用异常。 默认所有系统异常派生于 System.SystemException,所有的应用程序异常派生于 System.ApplicationException。 系统异常包括 OutOfMemoryException、IOException、NullReferenceException。 常用的异常类如下图所示。 ![异常](../images/异常.gif "异常") 常用的系统异常类如下表所示。 | 异常类 | 说明 | | ------------------------------- | ------------------- | | System.OutOfMemoryException | 用 new 分配内存失败 | | System.StackOverflowException | 递归过多、过深 | | System.NullReferenceException | 对象为空 | | Syetem.IndexOutOfRangeException | 数组越界 | | System.ArithmaticException | 算术操作异常的基类 | | System.DivideByZeroException | 除零错误 | ### **C#程序调试** 在 C# 语言中程序调试主要指在 Visual Studio 中调试程序,包括设置断点、监视断点,以及逐语句、逐过程、使用一些辅助窗口来调试程序。 在 Visual Studio 的菜单栏中单击“调试”,菜单项如下图所示。 ![调试](../images/调试.gif) 其中列出的内容即为调试时可用的选项,下面介绍其常用的调试功能。 #### **1) 设置断点** 所谓断点是程序自动进入中断模式的标记,即当程序运行到此处时自动中断。 在断点所在行的前面用红色的圆圈标记,设置标记时直接用鼠标单击需要设置断点的行前面的灰色区域即可,或者直接按键盘上的 F9 键。 例如在程序中设置断点,效果如下图 ![断点](../images/断点.gif "断点") 在设置断点时单击齿轮图标进入断点设置界面,如下图所示。 ![断点设置](../images/断点设置.gif "断点设置") 在该界面中允许为断点设置条件或操作,条件是指在满足指定条件时才会命中该断点。 此外,每个断点也允许设置多个条件,每个条件之间的关系是“与”的关系。界面如下图所示。 ![断点设置2](../images/断点设置2.gif "断点设置2") 在设置条件时可以设置条件表达式、命中次数以及筛选器。 其中: - 条件表达式是指一个布尔类型的表达式,如果满足条件则触发断点; - 命中次数若满足指定次数,则触发断点; - 筛选器用于限制只在某些进程和线程中设置断点。 在上图所示的界面中还可以为断点设置操作,用于指定在命中断点时打印追踪信息,界面如下图所示。 ![断点设置3](../images/断点设置3.gif "断点设置3") 在该界面中,如果在“将消息记录到输出窗口”文本框中输出断点“string[] str = new string[5];”处,其中 str 的值,则可以写成“str={str}”的形式,在调试输出窗口中会发现“str=Null”的信息输出。 此外,右击断点,弹出的右键菜单如下图所示。 ![删除断点](../images/删除断点.gif "删除断点") 在该菜单中选择“条件”或“操作”命令也可以完成对断点的上述设置。 #### **2) 管理断点** 在断点设置完成后,还可以在上图所示的菜单中选择进行“删除断点”“禁用断点”“编辑标签”“导出”的操作。 其中: - “删除断点”操作是取消当前断点,也可以再次单击断点的红点取消; - “禁用断点”操作是指暂时跳过该断点,将断点设置为禁用状态后,断点的右键菜单中的“禁用断点”选项- 更改为“启用断点”,在需要该断点时还可以选择“启用断点”恢复断点; - “编辑标签” 操作是为断点设置名称; - “导出”操作是将断点信息导出到一个 XML 文件中存放。 #### **3) 程序的调试过程** 在设置好断点后,调试程序可以直接按 F5 键,或者直接在菜单栏中选择“调试”→“开始调试”命令。 在调试程序的过程中,可以直接使用工具栏上的调试快捷键,如下图所示,或者直接在菜单栏中选择所需的调试命令。 ![断点状态栏](../images/断点状态栏.gif "断点状态栏") 下面介绍常用的调试命令。 - 逐语句(逐语句):按 F11 键也可以,用于逐条语句运行。 - 逐过程(逐过程):按 F10 键也可以,过程是指可以将方法作为一个整体去执行,不会跳进方法中执行。 - 跳出(跳出):按 Shift+F11 组合键也可以,跳出是将程序的调试状态结束,并结束整个程序。 此外,在调试过程中右击,会出现如下图所示的右键菜单。 ![调试菜单](../images/调试菜单.gif "调试菜单") 在该菜单中也可以选择相应的命令实现调试功能。 在调试过程中经常使用该菜单中的“运行到光标处”命令将程序执行到指定的光标处,忽略程序中设置的断点,用于快速调试程序和定位可能出错的位置。 #### **4) 监视器** 在调试程序的过程中经常需要知道某些变量的值在运行过程发生的变化,以便发现其在何时发生错误。 将程序中的变量或某个表达式放入监视器中即可监视其变化状态。 假设将 for 循环中的循环变量 i 加入监视器,在程序中右击变量 i,在弹出的如上图所示的菜单中选择“添加监视”命令,效果如下图所示。 ![监视](../images/监视.gif "监视") 从上图中可以看出,在监视器界面的“名称”列中是变量名、“值”列中是当前变量 i 的值,“类型”列中是当前变量的数据类型。 在一个监视器中可以设置多个需要监视的变量或表达式。 对于监视器中不需要再监视的变量,可以右击该变量,在弹出的右键菜单中选择“删除监视”命令,如下图所示。 ![监视菜单](../images/监视菜单.gif "监视菜单") 此外,通过上图所示的菜单还可以进行编辑值、复制值、全部清除等操作。 #### **5) 快速监视** 在调试程序时,如果需要监视变量或表达式的值也可以使用快速监视。 例如仍然要监视变量 i 的值,右击变量 i,在弹出的右键菜单中选择“快速监视”命令,弹出如下图所示的对话框。 ![快速监视](../images/快速监视.gif "快速监视") 通常,快速监视用于查看变量当前值的状态,与直接加入监视不同的是快速监视一次只能监视一个变量。 此外,在“快速监视”对话框处于打开状态时程序是无法继续调试的,如果需要继续监视“快速监视”对话框中的变量,可以单击“添加监视”按钮将当前监视的变量加入到监视器界面中。 #### **6) 即时窗口** 在调试程序时,如果需要对变量或表达式做相关运算,在即时窗口中都可以实现,并显示当前状态下变量或表达式的值。 在调试时可以使用“调试”菜单中“窗口”下的命令,在“调试”菜单中单击“窗口”出现如下图所示的子菜单。 ![即时窗口](../images/即时窗口.gif "即时窗口") 在其中选择“即时”命令即可出现即时窗口, 如下图所示。 ![即时输出](../images/即时输出.gif "即时输出") 在即时窗口中输入变量 i 的值并按回车键,即出现当前 i 在程序运行到此时的值。 ### **C# try catch finally:异常处理** 在 C# 语言中异常与异常处理语句包括三种形式,即 try catch、try finally、try catch finally。 在上述三种异常处理的形式中所用到关键字其含义如下: - try:用于检查发生的异常,并帮助发送任何可能的异常。 - catch:以控制权更大的方式处理错误,可以有多个 catch 子句。 - finally:无论是否引发了异常,finally 的代码块都将被执行。 下面我们将分别为大家讲解这三种形式的应用。 #### **1) try catch** 在 try 语句中放置可能出现异常的语句,而在 catch 语句中放置异常时处理异常的语句,通常在 catch 语句中输出异常信息或者发送邮件给开发人员等。 下面通过实例来演示 try catch 的应用。 另外,在处理异常时,catch 语句是允许多次使用的,相当于多分支的 if 语句,仅能执行其中一个分支。 ![未处理的异常](../images/未处理的异常.gif "未处理的异常") #### **2) try finally** 在 try finally 形式中没有单独对出现异常时处理的代码,finally 语句是无论 try 中的语句是否正确执行都会执行的语句。 通常在 finally 中编写的代码是关闭流、关闭数据库连接等操作,以免造成资源的浪费。 #### **3) try catch finally** try catch finally 形式语句是使用最多的一种异常处理语句。 在出现异常时能提供相应的异常处理,并能在 finally 语句中保证资源的回收。 ## **C# ADO.NET 数据库操作** 任何一个应用程序都离不开数据的存储,数据可以在内存中存储,但只能在程序运行时存取,无法持久保存。 数据还可以在磁盘中以文件的形式存储,但文件的管理和查找又十分烦琐无法胜任大数量的存储。 将数据存储到数据库中是在应用程序中持久存储数据的常用方式。 在 C# 语言中提供了 ADO.NET 组件来实现连接数据库以及操作数据库中数据的功能。 ### **C# ADO.NET 数据库操作及常用类概述** 在 C# 语言中 ADO.NET 是在 ADO 的基础上发展起来的,ADO (Active Data Object) 是一个 COM 组件类库,用于访问数据库,而 ADO.NET 是在 .NET 平台上访问数据库的组件。 ADO.NET 是以 ODBC (Open Database Connectivity) 技术的方式来访问数据库的一种技术。 ADO.NET 中的常用命名空间如下表所示。 | 命名空间 | 数据提供程序 | | ------------------------ | -------------------- | | System.Data.SqlClient | Microsoft SQL Server | | System.Data.Odbc | ODBC | | System.Data.OracleClient | Oracle | | System.Data.OleDb | OLE DB | 在使用 ADO.NET 进行数据库操作时通常会用到 5 个类,分别是 Connection 类、 Command 类、DataReader 类、DataAdapter 类、DataSet 类。 在接下来的讲解中我们将以连接 SQL Server 为例介绍 ADO.NET 中的对象,引用的命名空间为 System.Data.SqlClient。 除了 DataSet 类以外,其他对象的前面都加上 Sql,即 SqlConnection、SqlCommand、SqlDataReader、SqlDataAdapter。 #### Connection 类 该类主要用于数据库中建立连接和断开连接的操作,并且能通过该类获取当前数据库连接的状态。 使用 Connection 类根据数据库的连接串能连接任意数据库,例如 SQLServer、Oracle、MySQL 等。 但是在 .NET 平台下,由于提供了一个 SQL Server 数据库,并额外提供了一些操作菜单便于操作,所以推荐使用 SQLServer 数据库。 #### Command 类 该类主要对数据库执行增加、删除、修改以及查询的操作。 通过在 Command 类的对象中传入不同的 SQL 语句,并调用相应的方法来执行 SQL 语句。 #### DataReader 类 该类用于读取从数据库中查询出来的数据,但在读取数据时仅能向前读不能向后读, 并且不能修改该类对象中的值。 在与数据库的连接中断时,该类对象中的值也随之被清除。 #### DataAdapter 类 该类与 DataSet 联用,它主要用于将数据库的结果运送到 DataSet 中保存。 DataAdapter 可以看作是数据库与 DataSet 的一个桥梁,不仅可以将数据库中的操作结果运送到 DataSet 中,也能将更改后的 DataSet 保存到数据库中。 #### DataSet 类 该类与 DataReader 类似,都用于存放对数据库查询的结果。 不同的是,DataSet 类中的值不仅可以重复多次读取,还可以通过更改 DataSet 中的值更改数据库中的值。 此外,DataSet 类中的值在数据库断开连接的情况下依然可以保留原来的值。 ### **C# Connection:连接数据库** C# 语言中 Connection 类是 ADO.NET 组件连接数据库时第一个要使用的类,也是通过编程访问数据库的第一步。 接下来我们来了解一下 Connection 类中的常用属性和方法,以及如何连接 SQL Server 数据库。 #### Connection 类概述 Connection 类根据要访问的数据和访问方式不同,使用的命名空间也不同,类名也稍有区别,在这里我们使用的是 SqlConnection 类,以及微软提供的 SQL Server 2014 数据库。 SqlConnection 类中提供的常用属性和方法如下表所示。 | 属性或方法 | 说明 | | -------------------------------------- | ----------------------------------------------------------------- | | SqlConnection() | 无参构造方法 | | SqlConnection(string connectionstring) | 带参数的构造方法,数据库连接字符串作为参数 | | Connectionstring | 属性,获取或设置数据库的连接串 | | State | 属性,获取当前数据库的状态,由枚举类型 Connectionstate 为其提供值 | | ConnectionTimeout | 属性,获取在尝试连接时终止尝试并生成错误之前所等待的时间 | | DataSource | 属性,获取要连接的 SQL Server 的实例名 | | Open() | 方法,打开一个数据库连接 | | Close() | 方法,关闭数据库连接 | | BeginTransaction() | 方法,开始一个数据库事务 | #### 使用 Connection 类连接数据库 在使用 Connection 类连接 SQL Server 时,先要编写数据库连接串。 数据库连接串的书写方法有很多,这里介绍两种常用的方法。 第 1 种方式 ```C# server = 服务器名称 / 数据库的实例名 ; uid = 登录名 ; pwd = 密码 ; database = 数据库名称 ``` 其中: - server:用于指定要访问数据库的数据库实例名,服务器名称可以换成 IP 地址或者数据库所在的计算机名称,如果访问的是本机数据库,则可以使用“.”来代替,如果使用的是默认的数据库实例名,则可以省略数据库实例名。例如连接的是本机的默认数据库,则可以写成“server = .”。 - uid:登录到指定 SQL Server 数据库实例的用户名,相当于以 SQL Server 身份验证方式登录数据库时使用的用户名,例如 sa 用户。 - pwd:与 uid 用户对应的密码。 - database:要访问数据库实例下的数据库名。 第 2 种方式 ```C# Data Source = 服务器名称 \ 数据库实例名 ; Initial Catalog = 数据库名称 ; User ID = 用户名 ; Password = 密码 ``` 其中: - Data Source:与第 1 种连接串写法中的 server 属性的写法一样,用于指定数据库所在的服务器名称和数据库实例名,如果连接的是本机的默认数据库实例,则写成“Data Source=. ”的形式。 - Initial Catalog:与第 1 种连接串写法中的 database 属性的写法一样,用于指定在 Data - Source 中数据库实例下的数据库名。 - User ID:与第 1 种连接串写法中的 uid 属性的写法一样,用于指定登录数据库的用户名。 - Password:与第 1 种连接串写法中的 pwd 属性的写法一样,用于指定 User ID 用户名所对应的密码。 此外,还可以在连接字符串中使用 Integrate Security = True 的属性,省略用户名和密码,即以 Windows 身份验证方式登录 SQL Server 数据库。 使用 SqlConnection 类与数据库连接,分以下 3 步完成。 1. 创建 SqlConnection 类的实例 对于 SqlConnection 类来说,上表中提供了两个构造方法,通常是使用带一个字符串参数的构造方法来设置数据库的连接串创建其实例,语句形式如下。 ```C# SqlConnection 连接对象名 = new SqlConnection( 数据库连接串 ); ``` 2. 打开数据库连接 在创建 SqlConnection 连接类的实例后并没有连接上数据库,需要使用连接类的 Open 方法打开数据库的连接。 在使用 Open 方法打开数据库连接时,如果数据库的连接串不正确或者数据库的服务处于关闭状态,会出现打开数据库失败的相关异常,因此需要通过异常处理来处理异常。 打开数据库连接的语句形式如下。 ```C# 连接对象名.Open(); ``` 3. 关闭数据库连接 在对数据库的操作结束后要将数据库的连接断开,以节省数据库连接的资源。 关闭数据库连接的语句形式如下。 ```C# 连接对象名.Close(); ``` 如果在打开数据库连接时使用了异常处理,则将关闭数据库连接的语句放到异常处理的 finally 语句中,这样能保证无论是否发生了异常都将数据库连接断开,以释放资源。 除了使用异常处理的方式释放资源外,还可以使用 using 的方式释放资源。 具体的语句如下。 ```C# using(SqlConnection 连接对象名 = new SQLConnection( 数据库连接串 )) { //打开数据库连接 //对数据库先关操作的语句 } ``` using 关键字的用法主要有两个,一个是引用命名空间,一个是创建非托管资源对象。 在 .NET 平台上资源分为托管资源和非托管资源,托管资源是由 .NET 框架直接提供对其资源在内存中的管理,例如声明的变量;非托管资源则不能直接由 .NET 框架对其管理,需要使用代码来释放资源,例如数据库资源、操作系统资源等。 ### **C# Command:操作数据库** 操作数据库需则要用到 Command 类中提供的属性和方法。下面来介绍一下如何使用 Command 类来操作数据表中的数据。 #### **Command 类概述** 在 System.Data.SqlClient 命名空间下,对应的 Command 类为 SqlCommand,在创建 SqlCommand 实例前必须已经创建了与数据库的连接。 SqlCommand 类中常用的构造方法如下表所示。 | 构造方法 | 说明 | | ------------------------------------------------- | ----------------------------------------------------------------------------- | | SqlCommand() | 无参构造方法 | | SqlCommand(string commandText,SqlConnection conn) | 带参的构造方法,第 1 个参数是要执行的 SQL 语句,第 2 个参数是数据库的连接对象 | 对数据库中对象的操作不仅包括对数据表的操作,还包括对数据库、视图、存储过程等数据库对象的操作,接下来主要介绍的是对数据表和存储过程的操作。 在对不同数据库对象进行操作时,SqlCommand 类提供了不同的属性和方法,常用的属性和方法如下表所示。 | 属性或方法 | 说明 | | ----------------- | ---------------------------------------- | | CommandText | 属性,Command 对象中要执行的 SQL 语句 | | Connection | 属性,获取或设置数据库的连接对象 | | CommandType | 属性,获取或设置命令类型 | | Parameters | 属性,设置 Command 对象中 SQL 语句的参数 | | ExecuteReader() | 方法,获取执行查询语句的结果 | | ExecuteScalar() | 方法,返回查询结果中第 1 行第 1 列的值 | | ExecuteNonQuery() | 方法,执行对数据表的增加、删除、修改操作 | #### 使用 Command 类操作数据库 Command 类中提供了 3 种命令类型,分别是 Text、TableDirect 以及 StoredProcedure,默认情况下是 Text。 所谓 Text 类型是指使用 SQL 语句的形式,包括增加、删除、修改以及查询的 SQL 语句。 StoredProcedure 用于执行存储过程;TableDirect 仅在 OLE DB 驱动程序中有效。 在使用 Command 类操作数据库时需要通过以下步骤完成。 1. 创建 SqlCommand 类的实例 创建 SqlCommand 类的实例分两种情况,一种是命令类型为 Text 的,一种是命令类型为 StoredProcedure 的。 命令类型为 Text ```C# SqlCommand SqlCommand 类的实例名 = new SqlCommand( SQL 语句 , 数据库连接类的实例 ); ``` 其中: - SQL 语句:指该 SqlCommand 类的实例要执行的 SQL 语句。 - 数据库连接类的实例:指使用 SqlConnection 类创建的实例,通常数据库连接类的实例处于打开的状态。 命令类型为 StoredProcedure ```C# SqlCommand SqlCommand 类的实例名 = new SqlCommand( 存储过程名称 , 数据库连接类的实例 ); ``` 需要注意的是,存储过程必须是当前数据库实例中的存储过程,并且在调用带参数的存储过程时,还需要在 SqlCommand 类的实例中添加对应的存储过程参数。 为存储过程添加参数,需要使用 SqlCommand 类实例的 Parameters 属性来设置,具体的代码如下。 ```C# SqlCommand 类实例 .Parameters.Add( 参数名 , 参数值 ); ``` 在这里,参数名与存储过程中定义的参数名要一致。 2. 执行对数据表的操作 在执行对数据表的操作时通常分为两种情况,一种是执行非查询 SQL 语句的操作,即增加、修改、删除的操作,一种是执行查询 SQL 语句的操作。 执行非查询 SQL 语句的操作 在执行非查询 SQL 语句时并不需要返回表中的数据,直接使用 SqlCommand 类的 ExecuteNonQuery 方法即可,该方法的返回值是一个整数,用于返回 SqlCommand 类在执行 SQL 语句后,对表中数据影响的行数。 当该方法的返回值为 -1 时,代表 SQL 语句执行失败,当该方法的返回值为 0 时,代表 SQL 语句对当前数据表中的数据没有影响。 具体的代码如下。 ```C# qlCommand 类的实例 .ExecuteNonQuery(); ``` 需要注意的是,如果执行的 SQL 语句在数据库中执行错误,则会产生异常,因此该部分需要进行异常处理。 执行查询语句的操作 在执行查询语句时通常需要返回查询结果,SqlCommand 类中提供的 ExecuteReader 方法在执行查询 SQL 语句后,会返回一个 SqlDataReader 类型的值,通过遍历 SqlDataReader 类中的结果即可得到返回值。 具体的代码如下。 ```C# SqlDataReader dr = SqlCommand 类的实例 .ExecuteReader(); ``` 此外,如果在执行查询语句后并不需要返回所有的查询结果,而仅需要返回一个值,例如查询表中的记录行数,这时可以使用 ExecuteScalar 方法。具体的代码如下。 ```C# int returnvalue = SqlCommand 类的实例 .ExecuteScalar(); ``` ## C# DataSet ### 1.基本概念 可以把 DataSet 当成内存中的数据库,DataSet 是不依赖于数据库的独立数据集合。所谓独立,就是说,即使断开数据链路,或者关闭数据库,DataSet 依然是可用的,DataSet 在内部是用 XML 来描述数据的,由于 XML 是一种与平台无关、与语言无关的数据描述语言,而且可以描述复杂关系的数据,比如父子关系的数据,所以 DataSet 实际上可以容纳具有复杂关系的数据,而且不再依赖于数据库链路。 在典型的多层实现中,用于创建和刷新 DataSet 并依次更新原始数据的步骤包括: - 通过 DataAdapter 使用数据源中的数据生成和填充 DataSet 中的每个 DataTable。 - 通过添加、更新或删除 DataRow 对象更改单个 DataTable 对象中的数据。 - 调用 GetChanges 方法以创建只反映对数据进行的更改的第二个 DataSet。 - 调用 DataAdapter 的 Update 方法,并将第二个 DataSet 作为参数传递。 - 调用 Merge 方法将第二个 DataSet 中的更改合并到第一个中。 针对 DataSet 调用 AcceptChanges。或者,调用 RejectChanges 以取消更改。需要注意的是:dataset 所有数据都加载在内存上执行的,可以提高数据访问速度,提高硬盘数据的安全性。极大的改善了程序运行的速度和稳定性。 ### 2.数据模型 因为 DataSet 可以看做是内存中的数据库,也因此可以说 DataSet 是数据表的集合,它可以包含任意多个数据表(DataTable),而且每一 DataSet 中的数据表(DataTable)对应一个数据源中的数据表(Table)或是数据视图(View)。数据表实质是由行(DataRow)和 列(DataColumn)组成的集合为了保护内存中数据记录的正确性,避免并发访问时的读写冲突,DataSet 对象中的 DataTable 负责维护每一条记录,分别保存记录的初始状态和当前状态。从这里可以看出 DataSet 是与只能存放单张数据表的 Recordset 是截然不同的概念。 ### 3.使用介绍 #### 1、创建 ```C# DataSet ds = new DataSet("DataSetName"); ``` #### 2、查看调用 ```C# da.Fill(ds,"Orders"); DataTable tbl = ds.Table[0]; foreach(DataColumn col in tbl.Columns)  Console.WriteLine(col.ColumnName); ``` #### 3、查看 SqlDataAdapter 返回的数据 ```C# //DataRow对象 DataTable tbl = ds.Table[0]; DataRow row = tbl.Row[0]; Console.WriteLine(row["OrderID"]); //检查存储在DataRow中的数据 DataTable tbl = row.Table; foreach(DataColumn col in tbl.Columns)   Console.WriteLine(row[col]); //检查DatTable中的DataRow对象 foreach(DataRow row in tbl.Rows)   DisplayRow(row); ``` #### 4、校验 DataSet 中的数据 校验 DataColumn 的属性:ReadOnly,AllowDBNull,MaxLength,Unique DataTable 对象的 Constrains 集合:UiqueConstraints,Primarykey,ForeignkeyConstraints 通常不必刻意去创建 ForeignkeyConstraints,因为当在 DataSet 的两个 DataTable 对象之间创建关系时会创建一个。 用 SqlDataAdapter.Fill 模式来检索模式信息 ### 属性方法 属性 | | | | ------------------- | ------------------------------------------------------------ | | CaseSensitive | 用于控制 DataTable 中的字符串比较是否区分大小写。 | | DataSetName | 当前 DataSet 的名称。如果不指定,则该属性值设置为"NewDataSet"。如果将 DataSet 内容写入 XML 文件,DataSetName 是 XML 文件的根节点名称。 | | DesignMode | 如果在设计时使用组件中的 DataSet,DesignMode 返回 True,否则返回 False。 | | HasErrors | 表示 DataSet 中 的 DataRow 对象是否包含错误。如果将一批更改提交给数据库并将 DataAdapter 对象的 ContinueUpdateOnError 属性设置为 True,则在提交更改后必须检查 DataSet 的 HasErrors 属性,以确定是否有更新失败。 | | NameSpace 和 Prefix | 指定 XML 命名空间和前缀 | | Relations | 返回一个 DataRelationCollection 对象。 | | Tables | 检查现有的 DataTable 对象。通过索引访问 DataTable 有更好的性能。 | 方法 | | | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AcceptChanges 和 RejectChanges | 接受或放弃 DataSet 中所有挂起更改。调用 AcceptChanges 时,RowState 属性值为 Added 或 Modified 的所有行的 RowState 属性都将被设置为 UnChanged.任何标记为 Deleted 的 DataRow 对象将从 DataSet 中删除。调用 RejectChanges 时,任何标记为 Added 的 DataRow 对象将会被从 DataSet 中删除,其他修改过的 DatRow 对象将返回前一状态。 | | Clear | 清除 DataSet 中所有 DataRow 对象。该方法比释放一个 DataSet 然后再创建一个相同结构的新 DataSet 要快。 | | Clone 和 Copy | 使用 Copy 方法会创建与原 DataSet 具有相同结构和相同行的新 DataSet。使用 Clone 方法会创建具有相同结构的新 DataSet,但不包含任何行。 | | GetChanges | 返回与原 DataSet 对象具有相同结构的新 DataSet,并且还包含原 DataSet 中所有挂起更改的行。 | | GetXml 和 GetXmlSchema | 使用 GetXml 方法得到由 DataSet 的内容与她的架构信息转换为 XML 格式后的字符串。如果只希望返回架构信息,可以使用 GetXmlSchema。 | | HasChange | 表示 DataSet 中是否包含挂起更改的 DataRow 对象。 | | Merge | 从另一个 DataSet、DataTable 或现有 DataSet 中的一组 DataRow 对象载入数据。 | | ReadXml 和 WriteXml | 使用 ReadXml 方法从文件、TextReader、数据流或者 XmlReader 中将 XML 数据载入 DataSet 中。 | | Reset | 将 DataSet 返回为未初始化状态。如果想放弃现有 DataSet 并且开始处理新的 DataSet,使用 Reset 方法比创建一个 DataSet 的新实例好。 | ## **C# DataTable** ### (1)构造函数 | | | | -------------------------------------------------- | --------------------------------------------------- | | DataTable() | 不带参数初始化 DataTable 类的新实例 | | DataTable(string tableName) | 用指定的表名初始化 DataTable 类的新实例。 | | DataTable(string tableName, string tableNamespace) | 用指定的表名和命名空间初始化 DataTable 类的新实例。 | ### (2) 常用属性 | | | | --------------- | --------------------------------------------------------------------- | | CaseSensitive | 指示表中的字符串比较是否区分大小写。 | | ChildRelations | 获取此 DataTable 的子关系的集合。 | | Columns | 获取属于该表的列的集合。 | | Constraints | 获取由该表维护的约束的集合。 | | DataSet | 获取此表所属的 DataSet。 | | DefaultView | 获取可能包括筛选视图或游标位置的表的自定义视图。 | | HasErrors | 获取一个值,该值指示该表所属的 DataSet 的任何表的任何行中是否有错误。 | | MinimumCapacity | 获取或设置该表最初的起始大小。该表中行的最初起始大小。默认值为 50。 | | Rows | 获取属于该表的行的集合。 | | TableName | 获取或设置 DataTable 的名称。 | ### (3)常用方法 | | | | ---------------------- | ----------------------------------------------------------------------------- | | AcceptChanges() | 提交自上次调用 AcceptChanges() 以来对该表进行的所有更改。 | | BeginInit() | 开始初始化在窗体上使用或由另一个组件使用的 DataTable。初始化发生在运行时。 | | Clear() | 清除所有数据的 DataTable。 | | Clone() | 克隆 DataTable 的结构,包括所有 DataTable 架构和约束。 | | EndInit() | 结束在窗体上使用或由另一个组件使用的 DataTable 的初始化。初始化发生在运行时。 | | ImportRow(DataRow row) | 将 DataRow 复制到 DataTable 中,保留任何属性设置以及初始值和当前值。 | | Merge(DataTable table) | 将指定的 DataTable 与当前的 DataTable 合并。 | | NewRow() | 创建与该表具有相同架构的新 DataRow。 | ## DataTable 使用 ### 1. 添加引用 ```C# using System.Data; ``` ### 2.创建表 ```C# //创建一个空表 DataTable dt = new DataTable(); //创建一个名为"Table_New"的空表 DataTable dt = new DataTable("Table_New"); ``` ### 3.创建列 ```C# //1.创建空列 DataColumn dc = new DataColumn(); dt.Columns.Add(dc); //2.创建带列名和类型名的列(两种方式任选其一) dt.Columns.Add("column0", System.Type.GetType("System.String")); dt.Columns.Add("column0", typeof(String)); //3.通过列架构添加列 DataColumn dc = new DataColumn("column1",System.Type.GetType("System.DateTime")); DataColumn dc = new DataColumn("column1", typeof(DateTime)); dt.Columns.Add(dc); ``` ### 4.创建行 ```C# //1.创建空行 DataRow dr = dt.NewRow(); dt.Rows.Add(dr); //2.创建空行 dt.Rows.Add(); //3.通过行框架创建并赋值 dt.Rows.Add("张三",DateTime.Now);//Add里面参数的数据顺序要和dt中的列的顺序对应 //4.通过复制dt2表的某一行来创建 dt.Rows.Add(dt2.Rows[i].ItemArray); ``` ### 5.赋值和取值 ```C# //新建行的赋值 DataRow dr = dt.NewRow(); dr[0] = "张三";//通过索引赋值 dr["column1"] = DateTime.Now; //通过名称赋值 //对表已有行进行赋值 dt.Rows[0][0] = "张三"; //通过索引赋值 dt.Rows[0]["column1"] = DateTime.Now;//通过名称赋值 //取值 string name=dt.Rows[0][0].ToString(); string time=dt.Rows[0]["column1"].ToString(); ``` ### 6.筛选行 ```C# 选择column1列值为空的行的集合 DataRow[] drs = dt.Select("column1 is null"); //选择column0列值为"李四"的行的集合 DataRow[] drs = dt.Select("column0 = '李四'"); //筛选column0列值中有"张"的行的集合(模糊查询) DataRow[] drs = dt.Select("column0 like '张%'");//如果的多条件筛选,可以加 and 或 or //筛选column0列值中有"张"的行的集合并按column1降序排序 DataRow[] drs = dt.Select("column0 like '张%'", "column1 DESC"); ``` ### 7.删除行 ```C# //使用DataTable.Rows.Remove(DataRow)方法 dt.Rows.Remove(dt.Rows[0]); //使用DataTable.Rows.RemoveAt(index)方法 dt.Rows.RemoveAt(0); //使用DataRow.Delete()方法 dt.Row[0].Delete(); dt.AcceptChanges(); //-----区别和注意点----- //Remove()和RemoveAt()方法是直接删除 //Delete()方法只是将该行标记为deleted,但是还存在,还可DataTable.RejectChanges()回滚,使该行取消删除。 //用Rows.Count来获取行数时,还是删除之前的行数,需要使用DataTable.AcceptChanges()方法来提交修改。 //如果要删除DataTable中的多行,应该采用倒序循环DataTable.Rows,而且不能用foreach进行循环删除,因为正序删除时索引会发生变化,程式发生异常,很难预料后果。 for (int i = dt.Rows.Count - 1; i >= 0; i--) {   dt.Rows.RemoveAt(i); } ``` ### 8.复制表 ```C# //复制表,同时复制了表结构和表中的数据 DataTable dtNew = new DataTable(); dtNew = dt.Copy(); //复制表 DataTable dtNew = dt.Copy(); //复制dt表数据结构 dtNew.Clear() //清空数据 for (int i = 0; i < dt.Rows.Count; i++) { if (条件语句) { dtNew.Rows.Add(dt.Rows[i].ItemArray); //添加数据行 } } //克隆表,只是复制了表结构,不包括数据 DataTable dtNew = new DataTable(); dtNew = dt.Clone(); //如果只需要某个表中的某一行 DataTable dtNew = new DataTable(); dtNew = dt.Copy(); dtNew.Rows.Clear();//清空表数据 dtNew.ImportRow(dt.Rows[0]);//这是加入的是第一行 ``` ### 9.表排序 ```C# DataTable dt = new DataTable();//创建表 dt.Columns.Add("ID", typeof(Int32));//添加列 dt.Columns.Add("Name", typeof(String)); dt.Columns.Add("Age", typeof(Int32)); dt.Rows.Add(new object[] { 1, "张三" ,20});//添加行 dt.Rows.Add(new object[] { 2, "李四" ,25}); dt.Rows.Add(new object[] { 3, "王五" ,30}); DataView dv = dt.DefaultView;//获取表视图 dv.Sort = "ID DESC";//按照ID倒序排序 dv.ToTable();//转为表 ``` ## C# DataColumn ### 构造方法 | 构造方法 | 说明 | | ------------------------------------------- | -------------------------------------------------------------------------------- | | DataColumn() | 无参构造方法 | | DataColumn(string columnName) | 带参数的构造方法,columnName 参数代表的是列名 | | DataColumn(string columnName,Type dataType) | 带参数的构造方法,columnName 参数代表的是列名,dataType 参数代表的是列的数据类型 | ### 常用属性 | 属性 | 说明 | | ----------------- | -------------------------------------------------------------------------- | | ColumnName | 属性,设置 DataColumn 对象的列名 | | DataType | 属性,设置 DataColumn 对象的数据类型 | | MaxLength | 属性,设置 DataColumn 对象值的最大长度 | | Caption | 属性,设置 DataColumn 对象在显示时的列名,类似于给表中的列设置别名 | | DefaultValue | 属性,设置 DataColumn 对象的默认值 | | AutoIncrement | 属性,设置 DataColumn 对象为自动增长列,与 SQL Server 中数据表的标识列类似 | | AutoIncrementSeed | 属性,与 AutoIncrement 属性联用,用于设置自动增长列的初始值 | | AutoIncrementStep | 属性,与 AutoIncrement 属性联用,用于设置自动增长列每次增加的值 | | Unique | 属性,设置 DataColumn 对象的值是唯一的,类似于数据表的唯一约束 | | AllowDBNull | 属性,设置 DataColumn 对象的值是否允许为空 | DataColumn 是用来模拟物理数据库中的列。DataColumn 的组合组成了 DataTable 中列的架构。生成 DataTable 架构的方法就是向 DataColumnCollection 中添加 DataColumn 对象来生成架构。同物理数据库一样,列是有类型的,比如 varchar, datatime, int 等, DataColumn 有 DataType 属性表示这一列所存储的数据种类。由于 DataTable 所包含的数据通常合并回其原始数据源,因此必须使其数据类型与数据源中的数据类型匹配。 在物理数据库中,我们的列都要有各种约束来维持数据完整性,比如非空、唯一,同时也有各种自动化的操作,比如,自增。同样的在内存中,我们也可以这样定义,通过 AllowDBNull 、Unique 和 ReadOnly 等属性对数据的输入和更新施加限制,通过 AutoIncrement、AutoIncrementSeed 和 AutoIncrementStep 属性来实现数据自动生成。 ### 1.通过 DataColumn 创建列 ```C# private void MakeTable() { // Create a DataTable. DataTable table = new DataTable("Product"); // Create a DataColumn and set various properties. DataColumn column = new DataColumn(); column.DataType = System.Type.GetType("System.Decimal"); column.AllowDBNull = false; column.Caption = "Price"; column.ColumnName = "Price"; column.DefaultValue = 25; // Add the column to the table. table.Columns.Add(column); // Add 10 rows and set values. DataRow row; for(int i = 0; i < 10; i++) { row = table.NewRow(); row["Price"] = i + 1; // Be sure to add the new row to the // DataRowCollection. table.Rows.Add(row); } } ``` ### 2. 向数据表中添加列 DataColumn 的主要作用就是添加到 DataTable 中。添加的方法有两个:1. 显示的使用构造函数,然后将引用添加到集合中。2. 表内创建 DataColumn 对象,也就是在集合中直接添加。以下示例向 DataTable 中添加了四列。 ```C# DataTable workTable = new DataTable("Customers"); DataColumn workCol = workTable.Columns.Add("CustID", typeof(Int32)); workCol.AllowDBNull = false; workCol.Unique = true; workTable.Columns.Add("CustLName", typeof(String)); workTable.Columns.Add("CustFName", typeof(String)); workTable.Columns.Add("Purchases", typeof(Double)); ``` ### 3. 定义主键 在将一个单独的 DataColumn 标识为 DataTable 的 PrimaryKey 时,表会自动将列的 AllowDBNull 属性设置为 false,并将 Unique 属性设置为 true。 如果是多列主键,则只有 AllowDBNull 属性自动设置为 false。 ```C# //一列 workTable.PrimaryKey = new DataColumn[] {workTable.Columns["CustID"]}; // Or DataColumn[] columns = new DataColumn[1]; columns[0] = workTable.Columns["CustID"]; workTable.PrimaryKey = columns; //多列 workTable.PrimaryKey = new DataColumn[] {workTable.Columns["CustLName"], workTable.Columns["CustFName"]}; // Or DataColumn[] keyColumn = new DataColumn[2]; keyColumn[0] = workTable.Columns["CustLName"]; keyColumn[1] = workTable.Columns["CustFName"]; workTable.PrimaryKey = keyColumn; ``` ### 4.创建 AutoIncrement 列 只要将 AutoIncrement 属性设置为 true。此列的值就会自动递增了。可以设置递增的初始值,和步进大小。同时,要把 ReadOnly 属性设置为 true。 ```C# DataColumn workColumn = workTable.Columns.Add( "CustomerID", typeof(Int32)); workColumn.AutoIncrement = true; workColumn.AutoIncrementSeed = 200; workColumn.AutoIncrementStep = 3; ``` ## C# DataRow DataRow 类代表数据表中的行,并允许通过该类直接对数据表进行添加、修改、删除行的操作。 DataRow 类中常用的属性和方法如下表所示。 | 属性或方法 | 说明 | | --------------- | ------------------------------------------------------------ | | Table | 属性,设置 DataRow 对象所创建 DataTable 的名称 | | RowState | 属性,获取当前行的状态 | | HasErrors | 属性,获取当前行是否存在错误 | | AcceptChanges() | 方法,更新 DataTable 中的值 | | RejectChanges() | 方法,撤销对 DataTable 中的值的更新 | | Delete() | 方法,标记当前的行被删除,并在执行 AcceptChanges 方法后更新数据表 | ### 1. 添加行 创建新的 DataRow,要使用 DataTable 对象的 NewRow 方法。然后,使用 Add 方法将新的 DataRow 添加到 DataRowCollection 中。最后,调用 DataTable 对象的 AcceptChanges 方法以确认是否已添加。具体描述参考我另一篇文章 C# DataTable。 ```C# private void CreateNewDataRow() { // Use the MakeTable function below to create a new table. DataTable table; table = MakeNamesTable(); // Once a table has been created, use the // NewRow to create a DataRow. DataRow row; row = table.NewRow(); // Then add the new row to the collection. row["fName"] = "John"; row["lName"] = "Smith"; table.Rows.Add(row); foreach(DataColumn column in table.Columns) Console.WriteLine(column.ColumnName); dataGrid1.DataSource=table; } private DataTable MakeNamesTable() { // Create a new DataTable titled 'Names.' DataTable namesTable = new DataTable("Names"); // Add three column objects to the table. DataColumn idColumn = new DataColumn(); idColumn.DataType = System.Type.GetType("System.Int32"); idColumn.ColumnName = "id"; idColumn.AutoIncrement = true; namesTable.Columns.Add(idColumn); DataColumn fNameColumn = new DataColumn(); fNameColumn.DataType = System.Type.GetType("System.String"); fNameColumn.ColumnName = "Fname"; fNameColumn.DefaultValue = "Fname"; namesTable.Columns.Add(fNameColumn); DataColumn lNameColumn = new DataColumn(); lNameColumn.DataType = System.Type.GetType("System.String"); lNameColumn.ColumnName = "LName"; namesTable.Columns.Add(lNameColumn); // Create an array for DataColumn objects. DataColumn [] keys = new DataColumn [1]; keys[0] = idColumn; namesTable.PrimaryKey = keys; // Return the new DataTable. return namesTable; } /////////////////////////////////////////////////////// DataRow workRow; for (int i = 0; i <= 9; i++) { workRow = workTable.NewRow(); workRow[0] = i; workRow[1] = "CustName" + i.ToString(); workTable.Rows.Add(workRow); } ``` ### 2. 删除行 可通过调用 DataRowCollection 的 Remove 方法或调用 DataRow 对象的 Delete 方法,从 DataRowCollection 中删除 DataRow。Remove 方法将行从集合中移除。与此相反,Delete 标记要移除的 DataRow。在调用 AcceptChanges 方法时发生实际移除。通过调用 Delete,可在实际删除行之前以编程方式检查哪些行被标记为移除。 ```C# private void DemonstrateAcceptChanges() { //Run a function to create a DataTable with one column. DataTable table = MakeTable(); DataRow row; // Create a new DataRow. row = table.NewRow(); // Detached row. Console.WriteLine("New Row " + row.RowState); table.Rows.Add(row); // New row. Console.WriteLine("AddRow " + row.RowState); table.AcceptChanges(); // Unchanged row. Console.WriteLine("AcceptChanges " + row.RowState); row["FirstName"] = "Scott"; // Modified row. Console.WriteLine("Modified " + row.RowState); row.Delete(); // Deleted row. Console.WriteLine("Deleted " + row.RowState); } private DataTable MakeTable() { // Make a simple table with one column. DataTable table = new DataTable("table"); DataColumn fnameColumn = new DataColumn( "FirstName", Type.GetType("System.String")); table.Columns.Add(fnameColumn); return table; } ``` ## **C# DataGridView:数据表格控件数据绑定** 数据表格控件是 WinForm 窗体应用程序中用于查询时以表格形式显示数据的重要控件,同样数据表格控件也可以使用可视化数据绑定和代码的方式来绑定数据表中的数据,并能在数据表格控件中实现对表中数据的修改和删除操作。 下面分别介绍使用可视化数据绑定方式绑定数据表格控件和使用代码方式绑定数据表格控件。 ### 可视化方式绑定 DataGridView 控件 数据表格控件的可视化数据绑定也是通过控件的任务菜单完成的,如下图所示。 ![可视化](../images/可视化.gif "可视化") 在“DataGridView 任务”菜单中提供了“选择数据源”“编辑列”“添加列”,以及“启用添加”“启用编辑”“启用删除”“启用列重新排序”“在父容器中停靠”等选项。 其中: - 选择数据源:与组合框控件中选择数据源的操作是相同的。 - 编辑列:用于在 DataGridView 控件中编辑列,包括添加列、给列设置别名等操作。 - 添加列:用于向 DataGridView 控件中添加列,并且可以在 DataGridView 控件中添加不同类- 型的控件用于显示新添加的列,例如添加一个按钮用于修改或删除表中的数据。 - 启用添加:允许用户向 DataGridView 控件中添加一行,相当于将 DataGridView 控件中的 AllowUserToAddRows 属性设置为 True。 - 启用编辑:允许用户编辑 DataGridView 控件中的值,相当于将 DataGridView 控件中的 Readonly 属性设置为 False。 - 启用删除:允许用户删除 DataGridView 控件中的值,相当于将 DataGridView 控件中的 AllowUserToDeleteRows 属性设置为 True。 - 启用列重新排序:允许启用手动列重新设置,相当于将 DataGridView 控件中的 AllowUserToOrderColumn 属性设置为 True。 - 在父容器中停靠:允许 DataGridView 控件在所在的窗体中最大化。 ### 使用代码绑定 DataGridView 控件 使用代码绑定 DataGridView 控件时需要为该控件设置数据源 (DataSource) 属性,具 体的语句如下。 ```C# DataGridView 控件的名称.DataSource = DataTable 对象 ; ``` 如果使用 DataSet 对象为 DataSource 属性赋值,则需要使用 DataSet 对象的 Tables 属性选择指定的数据表。