Login_Form.cs 完整代码总结

好的!我给你写一份超详细、每一行都有注释 的完整代码,你直接覆盖原来的 Login_Form.cs 就行。


带超详细注释的 Login_Form.cs 完整代码

csharp 复制代码
// ============================================================
// 引入命名空间(相当于"导入工具箱")
// 每一行都是引入一个功能模块,后面代码才能用里面的类和方法
// ============================================================
using System;                                   // 基础功能(字符串、异常、时间等)
using System.Collections.Generic;               // 集合(列表、字典等)
using System.ComponentModel;                    // 组件模型(控件相关)
using System.Drawing;                           // 绘图、颜色、字体
using System.Linq;                              // LINQ查询
using System.Text;                              // 字符串处理
using System.Windows.Forms;                     // WinForms窗体(按钮、文本框、窗口等)
using System.Data;                              // 数据相关(数据表、数据集)
using System.Data.OleDb;                        // Access数据库操作
using System.Data.Sql;                          // SQL Server相关
using System.Data.SqlClient;                    // SQL Server客户端
using System.Threading;                         // 多线程、延时(Sleep)
using System.Diagnostics;                       // 进程、命令行(启动cmd.exe)
using System.Net.NetworkInformation;            // 网络相关(Ping命令)
using FibertopTest_Common;                      // 项目自己的公共类库(OTP12驱动、加热台驱动等)

// ============================================================
// 命名空间:SFP模块终测检查软件
// 这个项目的所有代码都在这个命名空间里
// ============================================================
namespace SFP模块终测检查软件
{
    /// <summary>
    /// 登录窗体类
    /// 作用:程序启动后第一个显示的窗口,用户在这里设置参数、连接设备,然后进入主界面
    /// 
    /// 继承关系:Login_Form 继承自 Form(窗体基类)
    /// partial 表示这个类的代码分成了几个文件,另一个文件是 Login_Form.Designer.cs(设计器自动生成的界面代码)
    /// </summary>
    public partial class Login_Form : Form
    {
        // ============================================================
        // 成员变量(这个类的"全局变量",整个类里的函数都能用)
        // ============================================================

        // 本地数据库文件存放路径
        // 原来用的是网络路径 X:\Fibertop\,现在改成本地路径
        //string filePath = "X:\\Fibertop\\"; // 网络数据库镜像(旧的,注释掉了)
        string filePath = "C:\\Fibertop\\";    // 本地数据库镜像(现在用这个)

        // 模块类型对应的数据库文件名数组
        // 数组大小是20,表示最多支持20种不同的光模块类型
        // 每种模块类型对应一个Access数据库文件,里面存着该模块的测试参数
        string[] moduleTypeDBName = new string[20];


        // ============================================================
        // 构造函数
        // 作用:创建 Login_Form 对象的时候自动调用,初始化窗体
        // ============================================================
        public Login_Form()
        {
            // 初始化组件(就是界面上的所有控件:按钮、文本框、下拉框等)
            // 这个方法是设计器自动生成的,在 Login_Form.Designer.cs 里
            InitializeComponent();
        }


        // ============================================================
        // 窗体加载事件
        // 触发时机:窗体刚显示出来的时候自动执行
        // 作用:做一些初始化工作,比如填默认值、创建对象等
        // ============================================================
        private void Login_Form_Load(object sender, EventArgs e)
        {
            // ============================================================
            // 1. 设置 SQL 服务器下拉框默认选中第1项
            // ============================================================
            // SelectedIndex = 0 表示选中第1项(索引从0开始,0是第1个,1是第2个...)
            sqlserver_comboBox.SelectedIndex = 0;

            // ============================================================
            // 2. 初始化两个设备对象(OTP12仪表 和 加热台)
            // ============================================================
            // GlobalVarFun 是全局变量类,在 Common.cs 里定义
            // 这里创建 OTP12 驱动对象,存到全局变量里,后面整个程序都能用
            GlobalVarFun.otp12 = new OTP12Driver();

            // 创建加热台驱动对象,存到全局变量里
            GlobalVarFun.heater = new SFP_EVB_Heater();

            // ============================================================
            // 3. 给两个IP文本框填上默认值
            // ============================================================
            // OTP12 的默认 IP 地址
            textBox_otp12Ip.Text = "192.168.100.156";

            // 加热台的默认 IP 地址(注意是129开头,不是192!)
            textBox_heaterIp.Text = "129.168.1.133";
        }


        // ============================================================
        // SQL服务器下拉框 选项改变事件
        // 触发时机:用户切换下拉框选项的时候
        // 作用:根据用户选的服务器IP,创建数据库连接对象
        // ============================================================
        private void sqlserver_comboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            // ============================================================
            // 创建 SQL Server 数据库连接对象
            // ============================================================
            // 连接字符串格式:server=服务器地址;uid=用户名;pwd=密码;database=数据库名
            // sqlserver_comboBox.Text 是用户选的服务器IP地址
            // 用户名:tester
            // 密码:fibertop2020
            // 数据库名:SFP
            //GlobalVarFun.sqlconnection = new SqlConnection("server=" + sqlserver_comboBox.Text + ";uid=sa;pwd=fiber123;database=SFP");
            GlobalVarFun.sqlconnection = new SqlConnection(
                "server=" + sqlserver_comboBox.Text + 
                ";uid=tester;pwd=fibertop2020;database=SFP");
        }


        // ============================================================
        // 测试 OTP12 连接 按钮点击事件
        // 触发时机:用户点击"测试连接"按钮(OTP12旁边的那个)
        // 作用:测试能不能连上 OTP12 设备
        // ============================================================
        private void button_testOtp12_Click(object sender, EventArgs e)
        {
            // ============================================================
            // 第1步:从文本框获取IP地址,去掉前后的空格
            // ============================================================
            // .Trim() 去掉字符串前后的空格,防止用户不小心输了空格
            string ip = textBox_otp12Ip.Text.Trim();

            // ============================================================
            // 第2步:检查IP地址是不是空的
            // ============================================================
            // 如果IP是空的(用户没输入),弹出提示,然后直接返回,不往下执行了
            if (string.IsNullOrEmpty(ip))
            {
                // 弹出提示框
                // 参数1:提示内容
                // 参数2:标题
                // 参数3:按钮类型(只有确定按钮)
                // 参数4:图标(警告图标)
                MessageBox.Show(
                    "请输入OTP12的IP地址!", 
                    "提示", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
                
                return;  // 直接退出函数,后面的代码不执行了
            }

            // ============================================================
            // 第3步:尝试连接 OTP12 设备
            // ============================================================
            try  // try-catch:异常处理,防止程序崩溃
            {
                // 调用 OTP12 驱动的 Connect 方法连接设备
                // 返回 true = 连接成功,返回 false = 连接失败
                if (GlobalVarFun.otp12.Connect(ip))
                {
                    // 连接成功,弹出成功提示
                    MessageBox.Show(
                        "OTP12连接成功!", 
                        "测试结果", 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Information);  // 信息图标(蓝色i)
                }
                else
                {
                    // 连接失败,弹出失败提示
                    MessageBox.Show(
                        "OTP12连接失败,请检查IP地址和网络!", 
                        "测试结果", 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);  // 错误图标(红色叉)
                }
            }
            catch (Exception ex)  // 如果上面的代码抛出异常,就走到这里
            {
                // 弹出错误信息,ex.Message 是异常的具体描述
                MessageBox.Show(
                    "OTP12连接出错:" + ex.Message, 
                    "错误", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            }
        }


        // ============================================================
        // 测试加热台连接 按钮点击事件
        // 触发时机:用户点击"测试连接"按钮(加热台旁边的那个)
        // 作用:测试能不能连上加热台设备
        // 逻辑和上面 OTP12 的几乎一样,只是调用的方法不同
        // ============================================================
        private void button_testHeater_Click(object sender, EventArgs e)
        {
            // 第1步:获取IP地址
            string ip = textBox_heaterIp.Text.Trim();

            // 第2步:检查IP是否为空
            if (string.IsNullOrEmpty(ip))
            {
                MessageBox.Show(
                    "请输入加热台的IP地址!", 
                    "提示", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
                return;
            }

            // 第3步:尝试连接加热台
            try
            {
                // 加热台用的是 Open 方法,不是 Connect(和OTP12不一样)
                if (GlobalVarFun.heater.Open(ip))
                {
                    MessageBox.Show(
                        "加热台连接成功!", 
                        "测试结果", 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Information);
                }
                else
                {
                    MessageBox.Show(
                        "加热台连接失败,请检查IP地址和网络!", 
                        "测试结果", 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    "加热台连接出错:" + ex.Message, 
                    "错误", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            }
        }


        // ============================================================
        // 测试 SQL 数据库连接 按钮点击事件
        // 触发时机:用户点击"测试连接"按钮(服务器IP旁边的那个)
        // 作用:测试能不能连上 SQL Server 数据库
        // ============================================================
        private void testSQL_button_Click(object sender, EventArgs e)
        {
            // ============================================================
            // 第1步:检查有没有选择服务器
            // ============================================================
            // 如果下拉框是空的,或者选的是"null",就提示用户
            if (string.IsNullOrEmpty(sqlserver_comboBox.Text) || 
                sqlserver_comboBox.Text == "null")
            {
                MessageBox.Show(
                    "请选择SQL数据库服务器!\r\n",  // \r\n 是换行符
                    "Warning", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
                return;
            }

            // ============================================================
            // 第2步:先 Ping 一下服务器,看网络通不通
            // ============================================================
            // 调用下面写的 TestServerIPonline() 函数
            // 返回 false = Ping不通,网络有问题
            if (TestServerIPonline() == false)
            {
                MessageBox.Show(
                    "服务器IP地址ping不通,网络不畅通,请检查网络连接!\r\n", 
                    "测试结果", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;  // Ping不通就直接返回,不往下试了
            }

            // ============================================================
            // 第3步:尝试打开数据库连接
            // ============================================================
            try
            {
                // 打开数据库连接
                GlobalVarFun.sqlconnection.Open();
                
                // 能打开说明连接成功,马上关掉(只是测试一下)
                GlobalVarFun.sqlconnection.Close();
                
                MessageBox.Show(
                    "测试连接成功", 
                    "测试结果", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Information);
            }
            catch (Exception exception)
            {
                // 连接失败,弹出错误信息
                MessageBox.Show(
                    "测试连接失败,请确认SQL数据库IP地址正确!!\r\n" + exception.Message, 
                    "测试结果", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
            }
        }


        // ============================================================
        // 测试服务器IP是否通畅(Ping 命令)
        // 这是一个辅助函数,被上面的 testSQL_button_Click 调用
        // 返回值:true = Ping通了,false = Ping不通
        // ============================================================
        private bool TestServerIPonline()
        {
            try  // 异常处理
            {
                // 创建 Ping 对象
                Ping ping = new Ping();
                
                // 发送 Ping 命令到服务器
                // sqlserver_comboBox.Text 是服务器IP地址
                // 返回 PingReply 对象,包含Ping的结果
                PingReply pingReply = ping.Send(sqlserver_comboBox.Text);
                
                // 释放 Ping 对象的资源
                ping.Dispose();

                // 检查 Ping 是否成功
                // IPStatus.Success 表示成功
                if (pingReply.Status != IPStatus.Success)
                {
                    return false;  // Ping失败,返回 false
                }
            }
            catch  // 如果出错(比如IP格式不对、网络断开等)
            {
                //catch (Exception exp)  // 原来的代码注释掉了异常变量
                return false;  // 出错了也返回 false
            }

            //
            return true;  // Ping成功,返回 true
        }


        // ============================================================
        // 从服务器复制 Access 数据库文件到本地
        // 这是一个辅助函数,被下面的 update_button_Click 调用
        // 返回值:true = 复制成功,false = 复制失败
        // 
        // 原理:通过命令行(cmd.exe)执行 net use 和 xcopy 命令
        //       把服务器共享文件夹里的Access数据库复制到本地
        // ============================================================
        private bool CopyShareDBFileToLocal()
        {
            // ============================================================
            // 变量定义
            // ============================================================
            Process proc = new Process();  // 进程对象,用来启动 cmd.exe
            string dosLine;                // 存命令行内容
            bool Flag = false;             // 成功标志:true=成功,false=失败

            try  // 异常处理
            {
                // ============================================================
                // 第1步:配置并启动 cmd.exe
                // ============================================================
                proc.StartInfo.FileName = "cmd.exe";         // 要启动的程序:命令行
                proc.StartInfo.UseShellExecute = false;      // 不使用系统shell启动
                proc.StartInfo.RedirectStandardInput = true; // 重定向输入(可以往里面写命令)
                proc.StartInfo.RedirectStandardOutput = true;// 重定向输出(可以读取输出)
                proc.StartInfo.RedirectStandardError = true; // 重定向错误输出
                proc.StartInfo.CreateNoWindow = true;        // 不显示黑色的cmd窗口
                proc.Start();                                // 启动 cmd.exe

                // ============================================================
                // 第2步:连接服务器的共享文件夹
                // ============================================================
                // net use 命令:连接网络共享文件夹
                // 格式:net use \\服务器IP\共享名 密码 /user:用户名
                // 服务器IP:sqlserver_comboBox.Text
                // 共享名:Fibertop
                // 密码:test2016
                // 用户名:fibertop
                dosLine = @"net use \\" + sqlserver_comboBox.Text + 
                    @"\Fibertop ""test2016"" /user:""fibertop""";
                
                // 把命令写入 cmd 的标准输入(相当于在cmd窗口里输入命令然后回车)
                proc.StandardInput.WriteLine(dosLine);

                // 延时1300毫秒(1.3秒),等命令执行完
                Thread.Sleep(1300);

                // ============================================================
                // 第3步:复制共享文件夹里的文件到本地 C:\
                // ============================================================
                // xcopy 命令:复制文件和文件夹
                // 格式:xcopy 源路径 目标路径 /s/e/y
                // /s:复制子目录(除了空的)
                // /e:复制子目录(包括空的)
                // /y:覆盖时不提示,直接覆盖
                dosLine = @"xcopy \\" + sqlserver_comboBox.Text + 
                    @"\Fibertop C:\ /s/e/y";
                proc.StandardInput.WriteLine(dosLine);

                // 延时300毫秒(0.3秒)
                Thread.Sleep(300);

                // ============================================================
                // 第4步:断开共享文件夹
                // ============================================================
                // net use /del 命令:断开网络共享
                dosLine = @"net use \\" + sqlserver_comboBox.Text + 
                    @"\飞思卓共享文件 /del";
                proc.StandardInput.WriteLine(dosLine);

                // 输入 exit 命令,退出 cmd
                proc.StandardInput.WriteLine("exit");
                //proc.StandardInput.Close();  // 原来的代码注释掉了

                // 等待 cmd 进程退出
                proc.WaitForExit();

                // ============================================================
                // 第5步:读取命令行输出,判断复制是否成功
                // ============================================================
                // 读取所有输出内容
                string str = proc.StandardOutput.ReadToEnd();

                // 如果输出里包含"复制了 0 个文件",说明复制失败了(一个文件都没复制到)
                if (str.Contains("复制了 0 个文件"))
                {
                    Flag = false;  // 复制失败
                }
                else
                {
                    Flag = true;   // 复制成功
                }

                // 弹出消息框,显示复制结果(复制了多少个文件等信息)
                MessageBox.Show(str);
            }
            catch (Exception ex)  // 如果出错
            {
                Flag = false;     // 标记为失败
                throw ex;         // 把异常抛出去,让调用者处理
            }
            finally  // finally:不管成功还是失败,都会执行这里的代码
            {
                // 关闭进程
                proc.Close();
                
                // 释放进程占用的资源
                proc.Dispose();
            }

            // 返回结果
            return Flag;
        }


        // ============================================================
        // 从服务器更新 按钮点击事件
        // 触发时机:用户点击"从服务器更新"按钮
        // 作用:
        //   1. 从服务器复制最新的Access数据库到本地
        //   2. 从本地数据库读取模块类型列表,填充到下拉框
        // ============================================================
        private void update_button_Click(object sender, EventArgs e)
        {
            // ============================================================
            // 变量定义
            // ============================================================
            int i = 0;  // 计数器,记录读到了多少种模块类型

            // ============================================================
            // 第1部分:处理数据库更新
            // ============================================================
            
            // 检查有没有选择服务器
            if (string.IsNullOrEmpty(sqlserver_comboBox.Text) || 
                sqlserver_comboBox.Text == "null")
            {
                // 没选服务器,就不更新,用本地现有的数据库
                GlobalVarFun.access_updated_status = false;
                
                // 弹出提示,告诉用户没选服务器,不更新
                MessageBox.Show(
                    "未选择服务器IP,系统将不更新本机Access文件,请确认!", 
                    "Warning", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
            }
            else  // 选了服务器,就尝试更新
            {
                // 先 Ping 一下服务器,看网络通不通
                if (TestServerIPonline() == false)
                {
                    MessageBox.Show(
                        "服务器IP地址ping不通,网络不畅通,请检查网络连接!\r\n", 
                        "测试结果", 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
                    return;  // Ping不通就直接返回
                }

                //
                // 从服务器复制 Access 数据库文件到本地
                if (CopyShareDBFileToLocal())
                {
                    // 复制成功
                    GlobalVarFun.access_updated_status = true;
                }
                else
                {
                    // 复制失败
                    GlobalVarFun.access_updated_status = false;
                    
                    // 弹出确认框,问用户要不要继续
                    // 如果用户点了"否",就直接返回,不往下执行了
                    if (MessageBox.Show(
                        "操作失败:从服务器更新Access文件到本地失败,请确认是否继续???", 
                        "提醒", 
                        MessageBoxButtons.YesNo, 
                        MessageBoxIcon.Warning) == DialogResult.No)
                    {
                        return;
                    }
                }
            }

            // ============================================================
            // 第2部分:从本地 Access 数据库读取模块类型列表
            // ============================================================
            
            // 先清空模块类型下拉框里的所有项
            type_comboBox.Items.Clear();

            try  // 异常处理
            {
                // ============================================================
                // 定义数据库操作相关的对象
                // ============================================================
                OleDbConnection dbconnect;     // 数据库连接对象
                OleDbCommand dbcommand;        // 数据库命令对象(执行SQL语句)
                OleDbDataAdapter dbadapter;    // 数据适配器(填充数据集)
                DataSet dbset;                 // 数据集(存放查询结果)
                string dbconnectionstr = "";   // 数据库连接字符串

                // ============================================================
                // 连接 Access 数据库
                // ============================================================
                // 数据库文件路径:filePath + "SupportedInfo.mdb"
                // filePath 是 C:\Fibertop\
                // 所以完整路径是 C:\Fibertop\SupportedInfo.mdb
                dbconnect = new OleDbConnection(
                    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " + 
                    filePath + "SupportedInfo.mdb");

                // ============================================================
                // 写 SQL 查询语句
                // ============================================================
                // 从 TypeInfo 表中查询 ModuleType(模块类型)和 AccessFilePath(数据库文件路径)
                // [TypeInfo] 是表名,加中括号是因为可能和关键字重名
                dbconnectionstr = string.Format(
                    "select ModuleType,AccessFilePath from [TypeInfo]");

                // 创建命令对象
                dbcommand = new OleDbCommand(dbconnectionstr, dbconnect);

                // 创建数据适配器
                dbadapter = new OleDbDataAdapter(dbcommand);

                // 创建数据集
                dbset = new DataSet();

                // 执行查询,把结果填充到数据集的 TypeInfo 表里
                dbadapter.Fill(dbset, "TypeInfo");

                // ============================================================
                // 遍历查询结果,填充到下拉框
                // ============================================================
                // 遍历 TypeInfo 表的每一行
                foreach (DataRow dataRow in dbset.Tables["TypeInfo"].Rows)
                {
                    // 如果模块类型名称不为空
                    if (dataRow["ModuleType"].ToString() != "")
                    {
                        // 把模块类型名称加到下拉框里
                        type_comboBox.Items.Add(dataRow["ModuleType"]);
                        
                        // 把对应的数据库文件路径存到数组里
                        // filePath + AccessFilePath = 完整的文件路径
                        moduleTypeDBName[i] = filePath + 
                            Convert.ToString(dataRow["AccessFilePath"]);
                        
                        // 计数器加1
                        i++;
                    }
                }

                // ============================================================
                // 释放数据库资源
                // ============================================================
                dbconnect.Close();       // 关闭连接
                dbcommand.Dispose();     // 释放命令对象
                dbadapter.Dispose();     // 释放适配器
                dbset.Dispose();         // 释放数据集
            }
            catch (Exception exp)  // 如果读数据库出错
            {
                // "确定"按钮变灰,不能点击(因为没读到模块类型,没法登录)
                ok_button.Enabled = false;
                
                // 弹出错误信息
                MessageBox.Show(exp.Message);
                
                return;  // 直接返回
            }

            // ============================================================
            // 设置下拉框默认选中项
            // ============================================================
            // 如果模块类型数量大于等于4个,默认选中第5个(索引是4,因为从0开始)
            // 为什么是4?可能是因为QSFP在第5个位置,这是常用的
            if (i >= 4)
            {
                type_comboBox.SelectedIndex = 4;
            }
        }


        // ============================================================
        // 确定按钮 点击事件 ⭐ 最重要的函数
        // 触发时机:用户点击"确定"按钮
        // 作用:
        //   1. 连接所有设备
        //   2. 初始化各种对象
        //   3. 全部成功后,关闭登录窗体,进入主界面
        // 
        // 【重要】登录窗体怎么进入主界面?
        //   1. 在 Program.cs 里,调用了 login_Form.ShowDialog()
        //   2. ShowDialog() 会阻塞,直到窗体关闭
        //   3. 当用户点击"确定"按钮时:
        //      - 这个函数里的代码执行完
        //      - 因为 ok_button 的 DialogResult 属性在设计器里设为了 OK
        //      - 所以窗体会自动关闭,并且 ShowDialog() 返回 DialogResult.OK
        //   4. Program.cs 里判断返回值是 OK,就调用 Application.Run(main_Form) 显示主界面
        // ============================================================
        private void ok_button_Click(object sender, EventArgs e)
        {
            // ============================================================
            // 第1步:连接 OTP12 设备
            // ============================================================
            string otp12Ip = textBox_otp12Ip.Text.Trim();  // 获取IP
            
            // 调用 Connect 方法连接,返回 false 表示失败
            if (!GlobalVarFun.otp12.Connect(otp12Ip))
            {
                // 连接失败,弹出提示
                MessageBox.Show(
                    "OTP12连接失败!\r\n请检查IP地址和网络连接", 
                    "警告", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                
                return;  // 直接返回,不往下执行了
            }

            // ============================================================
            // 第2步:连接加热台
            // ============================================================
            string heaterIp = textBox_heaterIp.Text.Trim();  // 获取IP
            
            // 调用 Open 方法连接,返回 false 表示失败
            if (!GlobalVarFun.heater.Open(heaterIp))
            {
                MessageBox.Show(
                    "加热台连接失败!\r\n请检查IP地址和网络连接", 
                    "警告", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;
            }

            // ============================================================
            // 第3步:初始化 I2C 通信对象
            // ============================================================
            // 原来的 I2C 是通过并口或USB转I2C的
            // 现在改成通过加热台转发 I2C 命令
            // I2C_Heater 就是我们写的加热台I2C实现类
            // 参数1:加热台对象
            // 参数2:当前槽位号(GlobalVarFun.cutrrentSlot,注意单词拼错了,应该是currentSlot)
            GlobalVarFun.iic = new I2C_Heater(
                GlobalVarFun.heater, 
                GlobalVarFun.cutrrentSlot);

            /////////////////////////////////////////////////////////////
            // 第4步:判断测试工序(初测 还是 终测)
            ///////////////////////////////////////////////////////////
            
            // 先清空测试类型
            GlobalVarFun.testType = "";

            // 如果选中了"初测"单选框(radioButton3)
            if (radioButton3.Checked)
            {
                GlobalVarFun.testType = "firstTest";  // 测试类型设为初测
            }

            // 如果选中了"终测"单选框(radioButton4)
            if (radioButton4.Checked)
            {
                GlobalVarFun.testType = "finalTest";  // 测试类型设为终测
            }

            ///////////////////////////////////////////////////////////

            // ============================================================
            // 第5步:测试 I2C 通信是否正常
            // ============================================================
            try
            {
                // 尝试打开 I2C
                if (GlobalVarFun.iic.TWI_Open() == false)
                {
                    throw new Exception();  // 打开失败,抛出异常
                }
                
                // 打开成功,马上关掉(只是测试一下能不能打开)
                GlobalVarFun.iic.TWI_Close();
            }
            catch
            {
                // I2C 初始化失败,弹出提示
                MessageBox.Show(
                    "I2C通信初始化失败!\r\n程序退出", 
                    "警告", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                
                Application.Exit();  // 直接退出整个程序
            }

            // ============================================================
            // 第6步:初始化模块测试对象
            // ============================================================
            // 根据用户选的模块类型,创建对应的测试对象
            // 目前只支持 QSFP
            if (type_comboBox.Text == "QSFP")
            {
                // 创建 QSFP 对象,转成 ModuleTest 基类类型,存到全局变量
                // ModuleTest 是基类,QSFP 是子类(继承自 ModuleTest)
                GlobalVarFun.mTest = new QSFP() as ModuleTest;
            }
            else
            {
                // 不支持的模块类型,弹出提示
                MessageBox.Show(
                    "模块类型初始化失败!\r\n程序退出", 
                    "警告", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                
                Application.Exit();  // 退出程序
            }

            // ============================================================
            // 第7步:设置 Access 数据库路径
            // ============================================================
            // 根据用户选的模块类型,从数组里取出对应的数据库文件路径
            // type_comboBox.SelectedIndex 是选中项的索引(0,1,2...)
            GlobalVarFun.moduleLutDBFilePath = moduleTypeDBName[type_comboBox.SelectedIndex];
            
            // 标记 Access 数据库连接状态为已连接
            GlobalVarFun.access_connect_status = true;

            // ============================================================
            // 第8步:判断 SQL 数据库要不要用
            // ============================================================
            // 如果没选服务器,或者选的是"null",就不用 SQL 数据库
            if (sqlserver_comboBox.Text.Trim() == "" || 
                sqlserver_comboBox.Text == "null")
            {
                GlobalVarFun.sqlconnection = null;  // 设为 null,表示不用
            }

            // ============================================================
            // 第9步:保存生产单号
            // ============================================================
            // 把用户输入的生产单号存到 TestResult 类的静态变量里
            // TestResult 类用来存测试结果相关的信息
            TestResult.fibertop_bn = bn_textBox.Text;

            // ============================================================
            // 【重要】函数执行完之后,会发生什么?
            // ============================================================
            // 因为 ok_button 按钮的 DialogResult 属性在设计器里设置成了 OK
            // 所以点击按钮后:
            //   1. 这个函数里的代码执行完
            //   2. 登录窗体会自动关闭
            //   3. ShowDialog() 方法返回 DialogResult.OK
            //   4. 回到 Program.cs 里,判断返回值是 OK,就打开主界面
        }


        // ============================================================
        // 模块类型下拉框 选项改变事件
        // 触发时机:用户切换模块类型下拉框的时候
        // 作用:
        //   1. 把选中的模块类型存到全局变量
        //   2. 把"确定"按钮设为可用(没选模块类型的时候,确定按钮是灰的)
        // ============================================================
        private void type_comboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            // 如果选中了某一项(索引 >= 0)
            if (type_comboBox.SelectedIndex >= 0)
            {
                // 把模块类型存到全局变量
                GlobalVarFun.moduleType = type_comboBox.Text;
                
                // "确定"按钮变成可用(可以点击了)
                ok_button.Enabled = true;
            }
        }


        // ============================================================
        // 读取飞思卓单号 按钮点击事件
        // 触发时机:用户点击这个按钮
        // 作用:目前是空的,还没实现功能
        // ============================================================
        private void readFibertopbn_button1_Click(object sender, EventArgs e)
        {
            //
            // 这个函数目前是空的,还没写代码
            // 可能是用来自动读取生产单号的,后面再实现
        }
    }
}

特别说明:登录界面怎么进入主界面?

很多人会困惑这个问题,我单独拿出来说一下:

流程是这样的:

复制代码
Program.cs 里
    ↓
创建 Login_Form 和 Main_Form 对象
    ↓
调用 login_Form.ShowDialog()  ← 程序停在这里,等登录窗体关闭
    ↓
用户在登录界面点"确定"按钮
    ↓
ok_button_Click 里的代码执行(连接设备、初始化等)
    ↓
因为 ok_button 的 DialogResult 属性在设计器里设为了 OK
所以窗体会自动关闭
    ↓
ShowDialog() 返回 DialogResult.OK
    ↓
Program.cs 里判断 if (login_Form.DialogResult == DialogResult.OK)
    ↓
调用 Application.Run(main_Form)
    ↓
主界面显示出来 ✅

关键点:

  • ShowDialog()模态对话框,会阻塞程序,直到窗体关闭
  • 按钮的 DialogResult 属性可以在设计器里设置,设为 OK 的话,点击按钮窗体会自动关闭
  • 关闭后 ShowDialog() 会返回对应的结果(OK、Cancel 等)