前言
现在有个需求,根据csv直接在winform窗体直接生成对应的按钮,接着将数据存进mysql数据库,方便下次直接调用,如果都不存在,友好提示
主要问题:
- 针对多个按钮分类,比如A区、B区、C区、D区
- 控制好各个按钮的边距和上下距离
- 文件读取与保存
- 读取的数据存进按钮,并赋值
- 多个按钮的时候,实现搜索功能提示
数据库格式,都为文本就好:
csv格式可选表头或者去掉表头
首先创建一个窗体(Windows窗体),在页面添加GroupPanel控件,方便分类、添加文本框,搜索按钮,如图所示:
配置文件,App.comf,为了方便修改,这里直接将数据保存与读取的地址在配置文件中,在文件中添加下面文件
<appSettings>
<add key="Text" value="C:\Users\DELL\Desktop\data.csv" />
</appSettings>
<connectionStrings>
<add name="conn" connectionString="Data Source=主机名称;Database=数据库;Uid=root;Pwd=密码;Port=端口号;charset=utf8;pooling=true" providerName="MySql.Data.MySqlClient" />
</connectionStrings>
DBConnection.cs
接着创建数据库连接类:DBConnection.cs (这个网络上很多大佬也有,大家可以任意查资料参考)
引入了与数据库相关的命名空间和类,包括MySql.Data.MySqlClient
和System.Data.SqlClient
。这些命名空间和类主要用于与数据库进行连接、查询和操作等操作
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace clickControls
{
internal class DBConnection
{
private MySqlConnection conn = null;
private MySqlCommand command = null;
private MySqlDataReader reader = null;
/// <summary>
/// 构造方法里建议连接
/// </summary>
/// <param name="connstr"></param>
public DBConnection(string connstr)
{
conn = new MySqlConnection(connstr);
}
/// <summary>
/// 检测数据库连接状态
/// </summary>
/// <returns></returns>
public bool CheckConnectStatus()
{
bool result = false;
try
{
conn.Open();
if (conn.State == ConnectionState.Open)
{
result = true;
}
}
catch
{
result = false;
}
finally
{
conn.Close();
}
return result;
}
/// <summary>
/// 输入sql语句,判断是否能运行
/// </summary>
/// <param name="tableName">要查询的表格</param>
/// <returns></returns>
public bool tableExists(string tableName)
{
string sql = $"SELECT 1 FROM {tableName} LIMIT 1";
try
{
conn.Open();
command = new MySqlCommand(sql, conn);
using (reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
reader.Read();
}
return true;
}
catch (Exception)
{
return false;
}
finally
{
conn.Close();
}
}
/// <summary>
/// 增、删、改公共方法
/// </summary>
/// <returns></returns>
public int commonExecute(string sql)
{
int res = -1;
try
{
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if (conn.State == ConnectionState.Open)
conn.Close();
conn.Open();
command = new MySqlCommand(sql, conn);
res = command.ExecuteNonQuery();
}
catch (MySqlException)
{
}
conn.Close();
return res;
}
/// <summary>
/// 查询方法
/// 注意:尽量不要用select * from table表(返回的数据过长时,DataTable可能会出错),最好指定要查询的字段。
/// </summary>
/// <returns></returns>
public DataTable query(string sql)
{
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if (conn.State == ConnectionState.Open)
conn.Close();
conn.Open();
command = new MySqlCommand(sql, conn);
DataTable dt = new DataTable();
using (reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(reader);
}
return dt;
}
/// <summary>
/// 将数据存进表格
/// </summary>
/// <param name="datatable">数据源</param>
/// <param name="table">表</param>
public void saveDataTableToMySQL(DataTable datatable ,string table)
{
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if (conn.State == ConnectionState.Open)
conn.Close();
conn.Open();
try
{
//创建数据库,如果数据库不存在的话
// 创建插入语句
String insertQuery = "INSERT INTO "+table+" (Name, address, notes) VALUES (?, ?, ?)";
// 创建预处理语句
MySqlCommand insertCommand = new MySqlCommand(insertQuery, conn);
// 遍历 DataTable 行并插入到数据库中
foreach (DataRow row in datatable.Rows)
{
insertCommand.Parameters.Clear(); // 清除现有参数
insertCommand.Parameters.AddWithValue("@Name", row[0].ToString());
insertCommand.Parameters.AddWithValue("@address", row[1].ToString());
insertCommand.Parameters.AddWithValue("@notes", row[2].ToString());
// 执行插入操作 ExecuteNonQuery 方法用于执行插入命令并返回受影响的行数。
insertCommand.ExecuteNonQuery();
}
}
catch (Exception)
{
throw;
}
conn.Close();
}
/// <summary>
/// 获取DataSet数据集
/// </summary>
/// <param name="sql"></param>
/// <param name="tablename"></param>
/// <returns></returns>
public DataSet GetDataSet(string sql, string tablename)
{
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if (conn.State == ConnectionState.Open)
conn.Close();
conn.Open();
command = new MySqlCommand(sql, conn);
DataSet dataset = new DataSet();
MySqlDataAdapter adapter = new MySqlDataAdapter(command);
adapter.Fill(dataset, tablename);
conn.Close();
return dataset;
}
/// <summary>
/// 实现多SQL语句执行的数据库事务
/// </summary>
/// <param name="SQLStringList">SQL语句集合(多条语句)</param>
public bool ExecuteSqlTran(List<string> SQLStringList)
{
bool flag = false;
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if (conn.State == ConnectionState.Open)
conn.Close();
conn.Open();
MySqlCommand cmd = conn.CreateCommand();
//开启事务
MySqlTransaction tran = this.conn.BeginTransaction();
cmd.Transaction = tran;//将事务应用于CMD
try
{
foreach (string strsql in SQLStringList)
{
if (strsql.Trim() != "")
{
cmd.CommandText = strsql;
cmd.ExecuteNonQuery();
}
}
tran.Commit();//提交事务(不提交不会回滚错误)
flag = true;
}
catch (Exception)
{
tran.Rollback();
flag = false;
}
finally
{
conn.Close();
}
return flag;
}
}
}
注:
要引用MySql.Data.MySqlClient
命名空间,需要先安装MySQL Connector/NET。可以通过以下几种方式来添加对该程序集的引用:
-
使用NuGet包管理器:打开Visual Studio,右键单击项目,选择"Manage NuGet Packages"。在NuGet包管理器中搜索"MySql.Data",然后选择安装MySQL Connector/NET。(vs推荐,方便好用)
-
手动下载DLL文件:你可以在MySQL官方网站上下载MySQL Connector/NET的安装包或者仅提取其中的DLL文件。将DLL文件添加到你的项目中,并在项目中引用该DLL文件。
-
添加引用:如果你已经安装了MySQL Connector/NET,并且知道其安装路径,可以手动添加对DLL文件的引用。打开Visual Studio,在解决方案资源管理器中,右键单击你的项目,然后选择"添加引用"。在弹出的对话框中,浏览到安装目录(通常是
C:\Program Files (x86)\MySQL\Connector.NET version\Assemblies
),选择适合你项目与.NET Framework版本的DLL文件,点击"确定"按钮添加引用。
对于System.Data.SqlClient
命名空间,不需要额外安装任何内容,这是.NET Framework中默认包含的。要使用System.Data.SqlClient
,只需在代码文件的开头添加以下using语句即可:
GetReader.cs
当创建好数据库连接工具后,可以创建读取csv文件(或者其他文本格式):
新建一个GetReader.cs类:GetReaders
类是一个内部类,它包含一个静态方法GetReader
用于获取Excel数据阅读器(IExcelDataReader
)。该方法根据文件扩展名选择使用适当的配置创建并返回相应的数据阅读器。
其中,ExcelDataReader
命名空间是用于读取和解析Excel文件的工具。在Visual Studio中,右键单击你的项目,选择"Manage NuGet Packages",然后搜索并安装ExcelDataReader
以及System.Text.Encoding.CodePages
。
关于GetReader
方法,它接受文件扩展名和文件流作为参数。根据扩展名不同,会创建不同类型的数据阅读器。如果扩展名是.csv
,则使用ExcelReaderFactory.CreateCsvReader
方法创建CSV文件的阅读器,并使用CodePagesEncodingProvider.Instance.GetEncoding(936)
设置默认编码;否则,将使用ExcelReaderFactory.CreateReader
方法创建普通Excel文件的阅读器。
如果需要在其他地方使用该方法,可以按照以下示例调用:
using (Stream stream = File.Open(filePath, FileMode.Open, FileAccess.Read))
{
string extension = Path.GetExtension(filePath);
IExcelDataReader reader = GetReaders.GetReader(extension, stream);
// 这里可以使用返回的数据阅读器(reader)来进行进一步的操作
}
filePath
是要读取的文件路径。
主程序:
- 在窗体的构造函数中调用
Init()
方法进行初始化操作。 - 在
Init()
方法中尝试打开文件流,并使用ExcelDataReader
库读取文件内容到数据表对象loadTable
中。 - 创建一个数据库连接对象,并查询数据库中的数据。
- 根据设备名称的不同,将数据分别添加到四个不同的列表中。
- 调用
AddButtonsToGroupPanel()
方法将设备按钮添加到相应的分组面板中。 - 在
AddButtonsToGroupPanel()
方法中,根据设备数量和窗体宽度动态计算面板的高度,并根据设备列表创建对应数量的按钮,并将按钮添加到分组面板中,并设置其位置和事件处理函数。 - 实现按钮的点击事件处理函数,弹出按钮的 Tag 属性值(设备地址)的消息框。
- 实现样式修改函数
HighlightButtons()
,用于修改指定设备名称的按钮样式,将字体加粗并改变文本颜色为红色。 - 实现关键字搜索按钮的点击事件处理函数
btnSearch_Click()
,根据用户输入的关键字,在所有分组面板中查找并高亮包含关键字的按钮。 - 实现恢复按钮样式的函数
RestoreButtonStyle()
,用于清除按钮的样式修改。
关键方法:
private void Init()
{
// 尝试打开文件流
try
{
fileStreaming = File.OpenRead(filePath);
//读取本地csv文件
using (var reader = GetReaders.GetReader(".csv", fileStreaming))
{
loadTable = reader.AsDataSet().Tables[0]; //默认第一张表
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to open file at path {filePath}: {ex.Message}");
}
// 连接数据库并查询数据
DBConnection conn = new DBConnection(connString);
string sqltable = "button_registration_form";
//先判断数据库有没有这个表
bool result = conn.tableExists(sqltable);
if (result) //有表
{
string selectQuery = "select * from " + sqltable;
DataTable dt = conn.query(selectQuery);
//有数据
if (dt.Rows.Count != 0)
{
ProcessData(dt);
}
else //没有数据,读取csv文件,并将数据存进mysql数据库
{
conn.saveDataTableToMySQL(loadTable, sqltable); //将数据存进mysql
ProcessData(loadTable);
}
}
else //没有,读取csv
{
ProcessData(loadTable);
}
}
先尝试读取一个 CSV 文件,如果文件存在并成功读取,则将数据存储在 loadTable
中;然后连接数据库并查询表是否存在,如果存在则根据表中是否已有数据来决定处理方式;如果不存在,则直接处理 loadTable
数据。具体的处理逻辑需要查看 ProcessData()
方法的实现。
private void ProcessData(DataTable dataTable) { //dataTable.Rows.RemoveAt(0); //去掉表头 if (dataTable == null) { MessageBox.Show("数据库没有数据,不存在本地文件"); return; } //遍历找出所有按钮 foreach (DataRow row in dataTable.Rows) { string deviceName = row[0].ToString(); if (deviceName.Contains("A区")) { beltDevices.Add(row); } else if (deviceName.Contains("B区")) { rollerDevices.Add(row); } else if (deviceName.Contains("C区")) { liftDevices.Add(row); } else { otherDevices.Add(row); } } AddButtonsToGroupPanel(beltDevices, 0); AddButtonsToGroupPanel(rollerDevices, 340); AddButtonsToGroupPanel(liftDevices, 680); AddButtonsToGroupPanel(otherDevices, 1020); }
将传入的数据表根据设备名称进行分类,并将每个设备类别对应的行数据添加到相应的列表中,最后将各个设备类别的按钮添加到分组面板中。
private void AddButtonsToGroupPanel(List<DataRow> devices, int locationX)
{
int panelWidth = 330; //窗体宽度
int buttonWidth = 100; //按钮宽度
int buttonHeight = 50; //按钮长度
int margin = 10; //边距
int x = 0; //按钮x位置
int y = 0; //按钮y位置
string panelText = (locationX == 0) ? "A区" : (locationX == 340) ? "B区" : (locationX == 680) ? "C区" : "其他";
GroupPanel groupPanel = new GroupPanel();
groupPanel.Location = new Point(locationX, 50); //窗体x轴地址偏移
groupPanel.SetDefaultPanelStyle();
groupPanel.Text = panelText; //窗体文本
//窗体工作区
int rowCount = (int)Math.Ceiling((double)(beltDevices.Count + rollerDevices.Count + liftDevices.Count + otherDevices.Count) / 4);
int maxDevicesCount = Math.Max(Math.Max(beltDevices.Count, rollerDevices.Count), Math.Max(liftDevices.Count, otherDevices.Count)); //最大值
int maxHeight = rowCount * buttonHeight ;
int panelHeight = maxDevicesCount * buttonHeight /3 + buttonHeight*3 +margin ; //单个窗体高度
this.ClientSize = new Size((panelWidth + margin) * 4, panelHeight +buttonHeight*3); //整体窗口
foreach (DataRow row in devices)
{
string deviceName = row[0].ToString();
string address = row[1].ToString();
ButtonX button = new ButtonX();
button.Size = new Size(buttonWidth, buttonHeight); //按钮默认大小
button.Click += new EventHandler(button_click);
button.Font = new Font(button.Font.FontFamily, 9, GraphicsUnit.Point);
button.Text = deviceName;
button.Tag = address;
//设计按钮位置
button.Location = new Point(x, y); //窗体X轴
groupPanel.Size = new Size(panelWidth, panelHeight); //窗体大小默认值
groupPa.Controls.Add(groupPanel);
groupPanel.Controls.Add(button);
x += buttonWidth + margin;
if (x + buttonWidth > panelWidth)
{
x = 0;
y += buttonHeight + margin;
}
}
}
AddButtonsToGroupPanel()
方法是一个私有方法,用于向分组面板中添加按钮。下面是方法的纯文本解析:
-
方法首先定义了一些参数:
panelWidth
:窗体宽度。buttonWidth
:按钮宽度。buttonHeight
:按钮长度。margin
:边距。
-
接下来,根据传入的
locationX
参数判断当前分组面板的设备类别,并设置相应的面板文本。 -
创建一个
GroupPanel
对象,并设置其位置、风格和文本。 -
计算窗体高度并设置窗体大小。
-
使用
foreach
循环遍历传入的设备列表中的每个设备。 -
对于每个设备,创建一个按钮,并设置其大小、点击事件、字体、文本和位置。
-
将按钮添加到分组面板中。
-
根据按钮的位置更新 x 和 y 的值。
-
将分组面板添加到窗体中。
作用是将设备列表中的设备按钮添加到对应的分组面板中。
private void btnSearch_Click(object sender, EventArgs e)
{
string buttonName = txtSearch.Text.Trim();
if (!string.IsNullOrEmpty(buttonName))
{
// 从 GroupPanel 集合中查找所有的 GroupPanel 控件
foreach (Control con in this.groupPa.Controls.OfType<GroupPanel>())
{
// 在每个 GroupPanel 控件中遍历所有的 ButtonX 控件
foreach (Control subCon in con.Controls)
{
if (subCon is ButtonX btn)
{
RestoreButtonStyle(btn); //清除样式
}
}
HighlightButtons(buttonName, con);
}
}
}
该方法用于在分组面板中搜索并高亮显示符合搜索条件的按钮。
private void HighlightButtons(string buttonName, Control control)
{
foreach (Control con in control.Controls)
{
if (con is ButtonX btn && btn.Text.Contains(buttonName))
{
btn.Font = new Font(btn.Font.FontFamily, 12, FontStyle.Bold, GraphicsUnit.Point);
btn.TextColor = Color.Red;
}
else
{
// 如果当前控件不是 ButtonX,就递归调用遍历它的子控件
HighlightButtons(buttonName, con);
}
}
}
它通过递归遍历控件及其子控件,来高亮显示符合搜索条件的按钮。
// 恢复按钮的样式
private void RestoreButtonStyle(ButtonX btn)
{
btn.TextColor = Color.Black;
btn.Font = new Font(btn.Font.FontFamily, 9, GraphicsUnit.Point);
}
最终,以上,整体代码结构好,接着,运行程序,其中,DBConnection类与GetReaders可以保存,下次需要的时候直接使用。
搜索效果: