一步一步学习Winform系列4:遗留问题与数据更新(修改、删除单条记录、批量删除)
新增过程中在按钮事件中对Datagridview的数据源内容进行如下判断
(1) 如果Datagridview还没有数据源,那么提示先点击按钮
(2) 如果Datagridview中数据源为部门表,则跳转到【新增部门信息】窗体
(3) 如果Datagridview中数据源为雇员表,则跳转到【新增雇员信息】窗体
(4) 其他情况以此类推
因此,我们需要调用Datagridview的属性dataGridView.Columns[0].HeaderText(第一列的标题)。点击主界面上的新增按钮时,先根据标题的内容进行判断,代码如下:
//点击新增按钮后根据datagridview的内容弹出对应的新增窗体 private void btnAdd_Click(object sender, EventArgs e) { //如果dataGridview的数据源是空的,所以还没有点击任何的按钮加载数据 if (dataGridView.DataSource == null) { MessageBox.Show("无法新增记录,请先点击要新增的对象按钮", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } //如果第一列的列标题是部门编号,说明当前datagridview加载的是部门表 else if (dataGridView.Columns[0].HeaderText == "部门编号") { Form new部门 = new 新增部门信息(NorthwindDAL.CommonFunc.GenerateNew部门编号()); new部门.ShowDialog(); //新增信息后可以直接返回到主界面进行刷新数据 DataBindByTableName("部门表"); } //如果第一列的列标题是雇员编号(前面我们已经进行中文转换了,否则你必须查看一下列标题是什么了)则跳转到新增雇员信息窗体 else if (dataGridView.Columns[0].HeaderText == "雇员编号") { //注意,这里我们像新增部门那样传递参数,而是在新窗体中自己产生新编号 Form new雇员 = new 新增雇员信息(); new雇员.ShowDialog(); DataBindByTableName("Employees"); } }
已经可以弹出相应的窗体了,那么忽略图片字段的情况下,大家先自行完成新增雇员窗体中的新增按钮代码,再对比后面的代码。
//新增雇员按钮点击事件 private void btnAdd_Click(object sender, EventArgs e) { //雇员名称不能为空 if (tbx雇员名称.Text == "") { MessageBox.Show("雇员名称不能为空,请重新输入!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); tbx雇员名称.Focus(); } else { //准备参数。注意:这里不需要为雇员编号添加参数,因为在数据库中编号会自动增加 string strSql = "INSERT INTO Employees(Dept,Password,FirstName,Title,HomePhone,Photo) VALUES(@所在部门,@登陆密码,@雇员名称,@头衔职务,@联系电话,@雇员照片)"; SqlParameter[] para = new SqlParameter[6]; para[0] = new SqlParameter("@所在部门", SqlDbType.NVarChar); para[0].Value = cbx所在部门.SelectedValue; para[1] = new SqlParameter("@登陆密码", SqlDbType.NVarChar); para[1].Value = "123456";//新雇员的登陆密码默认为123456 para[2] = new SqlParameter("@雇员名称", SqlDbType.NVarChar); para[2].Value = tbx雇员名称.Text; para[3] = new SqlParameter("@头衔职务", SqlDbType.NVarChar); para[3].Value = tbx头衔职务.Text; para[4] = new SqlParameter("@联系电话", SqlDbType.NVarChar); para[4].Value = tbx联系电话.Text; //关键字段 图片的申明与设置. //注意:这里需要判断,如果用户没有选择图片则赋予空值,有选择图片则赋予二进制文件流。 para[5] = new SqlParameter("@雇员照片", SqlDbType.Image); if (pictureBox.Image == null) { para[5].Value = null; } else { string strFilename = openFileDialog.FileName.ToString(); FileStream fs = new FileStream(strFilename,FileMode.Open,FileAccess.Read); BinaryReader br = new BinaryReader(fs); byte[] imgBytes = br.ReadBytes((int)fs.Length); para[5].Value = imgBytes; } int count = SqlHelper.ExecuteNonQuery(SqlHelper.GetConnection(), CommandType.Text, strSql, para); //判定是否有受影响的行数,如果有,说明插入成功了 if (count > 0) { MessageBox.Show("新雇员" + tbx雇员名称.Text + "插入成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); } } }
注意:这里的Insert语句我们需要指定具体的字段名,并且不需要为雇员编号添加参数,因为在数据库中这个编号会自动增加。因此这个语句是:
string strSql = "INSERT INTO Employees(Dept,Password,FirstName,Title,HomePhone) VALUES(@所在部门,@登陆密码,@雇员名称,@头衔职务,@联系电话)";
测试一下是否可以插入成功。
下面我们来看图片字段如何保存到数据库。
我们有很多窗体可能会涉及到图片,比如说产品、产品目录等等。这里以这里以雇员需要上传自己的图片为例。
如果保存图片需要分成两个步骤:
(1)选择图片按钮可以实现加载本地图片,这是需要打开文件控件——OpenFileDialog
//选择图片按钮点击事件 private void btnSelect_Click(object sender, EventArgs e) { //打开选择图片的对话框 //openFileDialog.ShowDialog(); //如果点击了对话框上的确认按钮,我们就把图片输出到picbox的控件内 if (openFileDialog.ShowDialog() == DialogResult.OK) { pictureBox.Load(openFileDialog.FileName); } }
(2)在新增按钮的点击事件里,对这个参数进行赋值,以实现新增到数据库
所以在新增雇员的按钮事件中需要注意最后一个参数的设置。需要将图片变成二进制文件存入数据库,因此需要using System.IO;才能调用FileStream
这个类。
//新增雇员按钮点击事件 private void btnAdd_Click(object sender, EventArgs e) { //雇员名称不能为空 if (tbx雇员名称.Text == "") { MessageBox.Show("雇员名称不能为空,请重新输入!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); tbx雇员名称.Focus(); } else { //准备参数。注意:这里不需要为雇员编号添加参数,因为在数据库中编号会自动增加 string strSql = "INSERT INTO Employees(Dept,Password,FirstName,Title,HomePhone,Photo) VALUES(@所在部门,@登陆密码,@雇员名称,@头衔职务,@联系电话,@雇员照片)"; SqlParameter[] para = new SqlParameter[6]; para[0] = new SqlParameter("@所在部门", SqlDbType.NVarChar); para[0].Value = cbx所在部门.SelectedValue; para[1] = new SqlParameter("@登陆密码", SqlDbType.NVarChar); para[1].Value = "123456";//新雇员的登陆密码默认为123456 para[2] = new SqlParameter("@雇员名称", SqlDbType.NVarChar); para[2].Value = tbx雇员名称.Text; para[3] = new SqlParameter("@头衔职务", SqlDbType.NVarChar); para[3].Value = tbx头衔职务.Text; para[4] = new SqlParameter("@联系电话", SqlDbType.NVarChar); para[4].Value = tbx联系电话.Text; //关键字段 图片的申明与设置. //注意:这里需要判断,如果用户没有选择图片则赋予空值,有选择图片则赋予二进制文件流。 para[5] = new SqlParameter("@雇员照片", SqlDbType.Image); if (pictureBox.Image == null) { para[5].Value = null; } else { string strFilename = openFileDialog.FileName.ToString(); FileStream fs = new FileStream(strFilename,FileMode.Open,FileAccess.Read); BinaryReader br = new BinaryReader(fs); byte[] imgBytes = br.ReadBytes((int)fs.Length); para[5].Value = imgBytes; } int count = SqlHelper.ExecuteNonQuery(SqlHelper.GetConnection(), CommandType.Text, strSql, para); //判定是否有受影响的行数,如果有,说明插入成功了 if (count > 0) { MessageBox.Show("新雇员" + tbx雇员名称.Text + "插入成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); } } }
为了能够在界面上看到效果,我们需要将【基本信息管理主界面】窗体中数据绑定按钮事件中隐藏的图片列取消设置。
//不显示某些列,只显示关键列 dataGridView.Columns["Password"].Visible = false; dataGridView.Columns["TitleOfCourtesy"].Visible = false; dataGridView.Columns["Address"].Visible = false; dataGridView.Columns["Extension"].Visible = false; dataGridView.Columns["Notes"].Visible = false; dataGridView.Columns["PhotoPath"].Visible = false; //dataGridView.Columns["Photo"].Visible = false;
运行一下,效果如图。
确定后会自动返回到主界面,我们可以看到新纪录
Ok,到目前为止,我们已经完整的学会的新增一条记录。
修改雇员信息比修改部门信息复杂一些,大家如果能实现修改雇员,那么修改部门信息不在话下了。
基本信息管理模块——修改雇员信息
先设计修改雇员信息的界面,它跟新增雇员信息没有很大的差别。只是在运行的时候需要能自动加载旧雇员的所有基本信息,包括原来的照片。
难点:获取datagridview选中行,列取相关记录(图片的显示),更新记录
(1) 基本信息管理主界面: 获取datagridview选中行中的雇员ID并传入修改雇员窗体。
//修改按钮点击事件,也必须根据datagridview内容判断弹出哪个窗体 private void btnUpdate_Click(object sender, EventArgs e) { //如果dataGridview的数据源是空的,所以还没有点击任何的按钮加载数据 if (dataGridView.DataSource == null) { MessageBox.Show("无法修改记录,请先点击要上方的按钮察看信息", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } //如果第一列的列标题是部门编号,说明当前datagridview加载的是部门表 else if (dataGridView.Columns[0].HeaderText == "部门编号") { //修改部门信息,请自行创建一个窗体,并完成此功能 } //如果第一列的列标题是雇员编号则跳转到修改雇员信息窗体 else if (dataGridView.Columns[0].HeaderText == "雇员编号") { //这里是修改,所以必须获取选中行的雇员ID后传值给新窗体 if (dataGridView.SelectedRows.Count > 0) { string selected雇员编号 = dataGridView.SelectedRows[0].Cells[0].Value.ToString(); Form old雇员 = new 修改雇员窗体(selected雇员编号); old雇员.ShowDialog(); DataBindByTableName("Employees"); } else { MessageBox.Show("无法修改记录,请先选中行", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } }
(2) 我们需要在弹出的修改雇员窗体上显示出已有的信息,尤其是图片(提示:窗体加载事件或者构造函数内都可以。这里我们写在修改雇员窗体的构造函数中)。
//构造函数 public 修改雇员窗体(string str雇员编号) { InitializeComponent(); tbx雇员编号.Text = str雇员编号; //部门信息加载到Combobox里 string strSql = "SELECT 部门名称 FROM 部门表"; DataSet ds = SqlHelper.ExecuteDataset(SqlHelper.GetConnection(), CommandType.Text, strSql); cbx所在部门.DataSource = ds.Tables[0]; cbx所在部门.DisplayMember = "部门名称"; cbx所在部门.ValueMember = "部门名称"; //根据传入的编号获取雇员的其他信息并赋值给其他控件 DataSet ds雇员信息 = SqlHelper.ExecuteDataset(SqlHelper.GetConnection(), CommandType.Text, "SELECT * FROM Employees WHERE EmployeeID="+tbx雇员编号.Text); tbx雇员名称.Text = ds雇员信息.Tables[0].Rows[0]["FirstName"].ToString(); cbx所在部门.SelectedValue = ds雇员信息.Tables[0].Rows[0]["Dept"].ToString(); tbx联系电话.Text = ds雇员信息.Tables[0].Rows[0]["HomePhone"].ToString(); tbx头衔职务.Text = ds雇员信息.Tables[0].Rows[0]["Title"].ToString(); //难点在于图片字段的读取啦 object img=ds雇员信息.Tables[0].Rows[0]["Photo"]; //如果有图片的话就读出图片 //if (OldImgBytes.Length > 0) //{ // MemoryStream ms = new MemoryStream(OldImgBytes, 0, OldImgBytes.Length); // pictureBox.Image = Image.FromStream(ms); // ms.Close();//释放内存文件流 //} if (img!=System.DBNull.Value) { OldImgBytes = (byte[])img; if (OldImgBytes.Length > 0) { MemoryStream ms; ms = new MemoryStream(OldImgBytes, 0, OldImgBytes.Length); pictureBox.Image = Image.FromStream(ms); ms.Close();//释放内存文件流 } } else { pictureBox.Image = null; } }
(3) 开始保存记录。我们先试着更新一下字段。
大家开动脑筋想一想,其实跟我们新增一条记录时的参数设置是一样的。唯一有变化的是SQL语句。
1、 自行百度UPDATE语句怎么写,设置好参数
2、 准备执行ExecuteNonQuery所需的各个参数(可以复制新增雇员信息的代码)
注意:这里不再需要设置Password字段了。
3、 测试一下修改信息(记得把图片顺便也更新一下)
修改成功了。
4、 但是有一个问题,如果用户不更新图片的会出现一个这样“空路径名是非法的”的提示.
这是因为用户没有重新选择图片,那么openfileDialog没有打开过,就没有任何的文件可供选择。也就是现在的文件路径为空。因此说明一个问题,照搬照抄是有风险的。
怎么改进呢?
我们需要申明一个全局变量OldImgBytes,再对@雇员照片赋值前需要经过三次判断:
1、如果image为空,说明原来数据库内该字段为空,则继续赋值空值
2、如果没有打开过openfiledialog,则必须保留原有的值
3、如果有文件名则用户更新了这个字段,以最新的文件为准保存入数据库
全局变量的申请
这个全局变量有两个函数用到,一个是刚刚加载进来的时候我们读取图片的时候,另一个就是para的赋值判断分支了。
那么修改雇员窗体的修改按钮的关键代码可以这么写:
//关键字段 图片的申明与设置. //保存记录时判断时如果原有值未经修改时,就赋值给全局变量OldImgBytes。 para[4] = new SqlParameter("@雇员照片", SqlDbType.Image); if (pictureBox.Image == null) { para[4].Value = null; } else if (openFileDialog.FileName !="") { string strFilename = openFileDialog.FileName.ToString(); FileStream fs = new FileStream(strFilename, FileMode.Open, FileAccess.Read); BinaryReader br = new BinaryReader(fs); byte[] imgBytes = br.ReadBytes((int)fs.Length); para[4].Value = imgBytes; } else { para[4].Value = OldImgBytes; } int count = SqlHelper.ExecuteNonQuery(SqlHelper.GetConnection(), CommandType.Text, strSql, para); //判定是否有受影响的行数,如果有,说明更新成功了 if (count > 0) { MessageBox.Show("修改雇员" + tbx雇员名称.Text + "信息成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); }
Ok,已经华丽丽的更新成功了。部门信息的更新大家自己完成啦。
我们要先进行单条记录删除操作,先设置datagridView的MultiSelect为False,这样一次只能选择一条记录,用于删除单条记录。
删除之前是需要做判断当前datagridView是什么表,也就是跟新增按钮一样的判断分支,还记得怎么写了吗?
麻烦又出现了,删除了数据我们却发现它不能自动刷新,必须重新点击一次才能看到删除后的效果,怎么办?
我们需要重新绑定一下数据,为了优雅的实现这个功能,我们打算改造一下我们的代码——建立公用的数据库绑定函数,也就是前面代码出现过的DataBindByTableName()函数。
/// <summary> /// 构造一个公用的datagridView绑定函数,根据数据库表名绑定不同的内容 /// </summary> /// <param name="strTableName">绑定的数据库表的名称</param> public void DataBindByTableName(string strTableName) { DataSet ds = SqlHelper.ExecuteDataset(SqlHelper.GetConnSting(), CommandType.Text, "SELECT * FROM "+strTableName); dataGridView.DataSource = ds.Tables[0]; } /// <summary> /// 根据数据库列标题名称绑定对应的内容 /// </summary> public void DataBindByDatagridviewHeadText() { string strHeadText = dataGridView.Columns[0].HeaderText; switch (strHeadText) { case "部门编号": DataBindByTableName("部门表"); break; case "雇员编号": DataBindByTableName("Employees"); break; case "CustomerID": DataBindByTableName("Customers"); break; case "CategoryID": DataBindByTableName("Categories"); break; default: DataBindByTableName("Suppliers"); break; } }
接着我们在删除按钮中设置一次绑定,这样,删完之后的效果局可以即时刷新了。
删除按钮的点击事件如下:
//删除按钮操作 private void btnDelete_Click(object sender, EventArgs e) { string strSql=""; int count; //如果dataGridview的数据源是空的,所以还没有点击任何的按钮加载数据 if (dataGridView.DataSource == null) { MessageBox.Show("无法删除记录,请先点击要删除的对象按钮", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } //如果用户确定要删除记录 else if (dataGridView.SelectedRows.Count == 0) { MessageBox.Show("无法删除记录,请先选中行", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else if (MessageBox.Show("确定删除这条记录吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) { //如果第一列的列标题是部门编号,说明当前datagridview加载的是部门表 if (dataGridView.Columns[0].HeaderText == "部门编号") { strSql = "DELETE FROM 部门表 WHERE 部门编号='" + dataGridView.SelectedRows[0].Cells[0].Value + "'"; } //如果第一列的列标题是雇员编号,说明当前datagridview加载的是雇员表 else if (dataGridView.Columns[0].HeaderText == "雇员编号") { strSql = "DELETE FROM Employees WHERE EmployeeID=" + dataGridView.SelectedRows[0].Cells[0].Value; } //赋值strSql结束后,执行相同的判断操作 count = SqlHelper.ExecuteNonQuery(SqlHelper.GetConnection(), CommandType.Text, strSql); if (count > 0) { MessageBox.Show("删除记录成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); //我们改造完了代码后,这里就可以轻飘飘的一句话带过了。请使用转到定义看明白这个代码。 DataBindByDatagridviewHeadText(); } else { MessageBox.Show("删除记录操作异常,请检查", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
//可选记录是否被选中 private void cbxMulti_CheckedChanged(object sender, EventArgs e) { if (cbxMulti.Checked) dataGridView.MultiSelect = true; else dataGridView.MultiSelect = false; }
如果要批量删除则确认已经选中【可选多条记录】。真的有很多代码,这里跟前面单条删除操作的区别在于我们必须一条条的去删我们选中的行,因此用到了循环结构啦。呵呵。看看看看。
//批量删除按钮点击事件 private void btnDeleMulti_Click(object sender, EventArgs e) { if (!cbxMulti.Checked) { MessageBox.Show("如果要批量删除,请点击前面的可选多条记录按钮!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { string strSql = ""; int count; //如果dataGridview的数据源是空的,所以还没有点击任何的按钮加载数据 if (dataGridView.DataSource == null) { MessageBox.Show("无法批量删除记录,请先确定有记录可供删除", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } //如果用户确定要删除记录 else if (dataGridView.SelectedRows.Count == 0) { MessageBox.Show("无法删除记录,请先选中行", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else if (MessageBox.Show("确定删除这些记录吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) { for (int i = 0; i < dataGridView.SelectedRows.Count; i++) { //如果第一列的列标题是部门编号,说明当前datagridview加载的是部门表 if (dataGridView.Columns[0].HeaderText == "部门编号") { strSql = "DELETE FROM 部门表 WHERE 部门编号='" + dataGridView.SelectedRows[i].Cells[0].Value + "'"; } //如果第一列的列标题是雇员编号,说明当前datagridview加载的是雇员表 else if (dataGridView.Columns[0].HeaderText == "雇员编号") { strSql = "DELETE FROM Employees WHERE EmployeeID=" + dataGridView.SelectedRows[i].Cells[0].Value; } else if (dataGridView.Columns[0].HeaderText == "CustomerID") { strSql = "DELETE FROM Customers WHERE CustomerID='" + dataGridView.SelectedRows[i].Cells[0].Value + "'"; } //赋值strSql结束后,执行相同的判断操作 count = SqlHelper.ExecuteNonQuery(SqlHelper.GetConnection(), CommandType.Text, strSql); if (count<= 0) { MessageBox.Show("删除记录操作异常,请检查", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error); } } MessageBox.Show("删除记录成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); //我们改造完了代码后,这里就可以轻飘飘的一句话带过了。请使用转到定义看明白这个代码。 DataBindByDatagridviewHeadText(); } } }
建议大家在这个测试数据库里面以部门表为例,因为其他表都设置了关联约束,不能随便删除的。。。。。。。
恭喜恭喜,到目前为止,你已经能完成了一个信息系统的CRUD开发啦。