C# WinForms 架构与项目实战:多窗体通信、三层架构、数据库、日志与配置

C# WinForms 架构与项目实战:多窗体通信、三层架构、数据库、日志与配置

引言

前两篇分别讲了 WinForms 的入门基础进阶技术。但单个窗口、单个功能写得再好,也不等于能交付一个可维护的项目

真正的项目开发,绕不开这五件事:怎么管理多个窗体、怎么分层、怎么连数据库、出错怎么兜底、配置放哪里。

这篇就把这五块讲透,让你从"会写控件"跨到"能搭项目"。

代码基于 .NET 6/8 + Visual Studio 2022,数据库以 SQL Server 为例(System.Data.SqlClientMicrosoft.Data.SqlClient),.NET Framework 4.x 同样适用。

一、多窗体开发与窗体通信

一个项目必然有多个窗体。核心问题是:窗体之间怎么传数据、怎么回调结果。

1.1 模态与非模态

csharp 复制代码
// 模态:阻塞当前窗体,直到子窗体关闭(常用于对话框)
using (var f = new EditForm())
{
    if (f.ShowDialog(this) == DialogResult.OK)
    {
        // 用户点了确定
    }
}

// 非模态:两者可同时操作
var f2 = new MonitorForm();
f2.Show(this);   // this 作为 Owner,子窗体始终在父窗体之上

1.2 五种传值方式

方式一:构造传参(最简单,适合一次性传入)

csharp 复制代码
public class EditForm : Form
{
    private readonly int _id;
    public EditForm(int id)
    {
        _id = id;
        InitializeComponent();
        LoadData(_id);
    }
}
// 调用:new EditForm(123).ShowDialog();

方式二:公开属性(适合双向传值)

csharp 复制代码
public class EditForm : Form
{
    public string UserName { get; set; }   // 父窗体写入
    public bool Saved { get; private set; } // 父窗体读取结果
}

// 父窗体
var f = new EditForm { UserName = "张三" };
f.ShowDialog();
if (f.Saved) RefreshList();

方式三:事件回调(子窗体主动通知父窗体,耦合最松)

csharp 复制代码
public class EditForm : Form
{
    public event Action<string>? DataSaved;   // 保存成功时回调

    private void btnSave_Click(object sender, EventArgs e)
    {
        // ... 保存逻辑
        DataSaved?.Invoke(textBox1.Text);
        Close();
    }
}

// 父窗体订阅
var f = new EditForm();
f.DataSaved += name => listBox1.Items.Add(name);
f.Show();

方式四:DialogResult(标准的"确定/取消"结果)

csharp 复制代码
// 子窗体:确定按钮的 DialogResult = DialogResult.OK,取消 = Cancel
// 父窗体通过 ShowDialog() 的返回值判断

方式五:静态服务/中介者(跨多个窗体共享状态,如"当前登录用户")

csharp 复制代码
public static class Session
{
    public static string CurrentUser { get; set; }
}

1.3 选型建议

一次性传参用构造或属性;需要"子窗体通知父窗体刷新"用事件;简单的"确定/取消"用 DialogResult;全局状态用静态服务。

二、三层架构

三层架构把代码分成表示层(UI)、业务逻辑层(BLL)、数据访问层(DAL) ,核心目的:UI 不碰 SQL,业务规则集中,数据访问可替换。

复制代码
WinFormsApp
 ├─ Models/          # 数据模型(实体类)
 ├─ DAL/             # 数据访问层:只负责存取数据库
 ├─ BLL/             # 业务逻辑层:校验、规则、流程
 └─ UI/              # 表示层:窗体、控件,只调用 BLL

2.1 分层示例

csharp 复制代码
// Models/User.cs ------ 实体
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// DAL/UserRepository.cs ------ 数据访问层
public class UserRepository
{
    public User GetById(int id)
    {
        // 只写 SQL 和参数,不写业务规则
        using var conn = DbHelper.GetConnection();
        using var cmd = new SqlCommand("SELECT * FROM Users WHERE Id=@id", conn);
        cmd.Parameters.AddWithValue("@id", id);
        conn.Open();
        using var reader = cmd.ExecuteReader();
        return reader.Read()
            ? new User { Id = id, Name = reader["Name"].ToString()!, Email = reader["Email"].ToString()! }
            : null!;
    }
}

// BLL/UserService.cs ------ 业务逻辑层
public class UserService
{
    private readonly UserRepository _repo = new();
    public User GetUser(int id)
    {
        var user = _repo.GetById(id);
        if (user == null)
            throw new Exception("用户不存在");
        // 这里可以加业务校验、缓存、权限等
        return user;
    }
}

// UI ------ 表示层
private void btnQuery_Click(object sender, EventArgs e)
{
    var service = new UserService();
    var user = service.GetUser(int.Parse(textBox1.Text));
    label1.Text = user.Name;
}

关键原则:调用方向只能"从上到下"------UI 调 BLL,BLL 调 DAL,绝不能反过来,也不能 UI 直接调 DAL。实体类(Model)作为层与层之间的"运输工具"贯穿三层。

三、数据库操作

WinForms 传统做法是 ADO.NET,简单直接;追求效率可用轻量 ORM(Dapper)。

3.1 防 SQL 注入:参数化查询

绝对不要拼接 SQL 字符串,用参数化:

csharp 复制代码
string sql = "SELECT * FROM Users WHERE Name = @name";
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@name", textBox1.Text);   // 参数化,防注入

3.2 一个 DbHelper 封装

csharp 复制代码
public static class DbHelper
{
    private static readonly string ConnStr =
        ConfigurationManager.AppSettings["ConnStr"]!;

    public static SqlConnection GetConnection() => new(ConnStr);

    // 执行查询,返回 DataTable
    public static DataTable Query(string sql, params SqlParameter[] ps)
    {
        using var conn = GetConnection();
        using var cmd = new SqlCommand(sql, conn);
        cmd.Parameters.AddRange(ps);
        using var adapter = new SqlDataAdapter(cmd);
        var dt = new DataTable();
        adapter.Fill(dt);
        return dt;
    }

    // 执行增删改,返回影响行数
    public static int Execute(string sql, params SqlParameter[] ps)
    {
        using var conn = GetConnection();
        using var cmd = new SqlCommand(sql, conn);
        cmd.Parameters.AddRange(ps);
        conn.Open();
        return cmd.ExecuteNonQuery();
    }
}

要点:

  1. SqlConnectionSqlCommand 都要 using,确保连接归还连接池。
  2. 增删改用 ExecuteNonQuery,查单个值用 ExecuteScalar,查多行用 SqlDataAdapterExecuteReader
  3. 事务用 SqlTransaction,多条写操作要么全成功要么全回滚。

3.3 用 Dapper 简化

不想手写映射,用 Dapper 一行搞定:

csharp 复制代码
using Dapper;
var users = conn.Query<User>("SELECT * FROM Users WHERE Age > @age",
    new { age = 18 }).ToList();

四、异常处理与日志

4.1 局部异常:try-catch-finally

csharp 复制代码
try
{
    var user = new UserService().GetUser(id);
}
catch (Exception ex)
{
    Log.Error(ex);                       // 记录日志
    MessageBox.Show("操作失败:" + ex.Message);  // 给用户友好提示
}

原则:捕获你能处理的异常 ,别无脑 catch 后吞掉;finally 里释放资源。

4.2 全局兜底:未捕获异常

用户操作触发的任何异常,都不该让程序直接崩掉。在 Program.cs 里注册全局处理:

csharp 复制代码
[STAThread]
static void Main()
{
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

    // UI 线程异常
    Application.ThreadException += (s, e) =>
    {
        Log.Error(e.Exception);
        MessageBox.Show("发生错误:" + e.Exception.Message, "错误",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    };

    // 非 UI 线程异常(进程即将崩溃,只能记录)
    AppDomain.CurrentDomain.UnhandledException += (s, e) =>
        Log.Error(e.ExceptionObject as Exception);

    Application.Run(new MainForm());
}

4.3 日志框架

手写写文件不如用成熟框架,推荐 NLoglog4net。以 NLog 为例:

csharp 复制代码
// 全局静态日志器
public static class Log
{
    private static readonly NLog.Logger Logger =
        NLog.LogManager.GetCurrentClassLogger();
    public static void Error(Exception ex) => Logger.Error(ex);
    public static void Info(string msg) => Logger.Info(msg);
}

NLog.config 配置输出到文件 + 控制台,支持按日期滚动、按级别过滤。生产环境务必配好日志,否则线上问题无从排查。

五、配置文件管理

配置(连接串、接口地址、开关)要写进配置文件,而不是硬编码在代码里,改了不用重新编译。

5.1 .NET Framework:App.config

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="ConnStr" value="Server=.;Database=MyDb;Trusted_Connection=True;" />
    <add key="ApiUrl" value="https://api.example.com" />
  </appSettings>
</configuration>

读取(需引用 System.Configuration):

csharp 复制代码
string conn = ConfigurationManager.AppSettings["ConnStr"]!;

5.2 .NET 6/8:appsettings.json

json 复制代码
{
  "ConnectionStrings": {
    "Default": "Server=.;Database=MyDb;Trusted_Connection=True;"
  },
  "AppSettings": {
    "ApiUrl": "https://api.example.com"
  }
}

读取(需安装 Microsoft.Extensions.Configuration.Json):

csharp 复制代码
var config = new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .Build();

string conn = config.GetConnectionString("Default")!;
string apiUrl = config["AppSettings:ApiUrl"]!;

要点:.json 文件要设置"复制到输出目录=始终复制";敏感信息(密码、密钥)别明文入库,用环境变量或加密。

总结

架构和实战,拼的是组织代码的能力

  1. 多窗体通信先想清楚数据流向,再选传值方式,别一上来就全局静态变量满天飞。
  2. 三层架构让"UI 不碰 SQL、业务规则集中",是 WinForms 项目可维护的关键。
  3. 数据库操作必须参数化 ,连接和命令用完 using
  4. 异常要分两层:局部 try-catch 给友好提示,全局 ThreadException 兜底,配日志框架落盘。
  5. 配置外置到 App.config/appsettings.json,别硬编码。

把前两篇的技术 + 这篇的架构串起来,你就拥有了交付一个完整 WinForms 桌面项目的能力。下一篇想深入哪个方向(比如完整的权限管理、Dapper + 三层架构的完整 Demo 项目),评论区告诉我。


本文为原创,转载注明出处。有用的话,点赞、收藏、关注,感谢支持!

相关推荐
许彰午1 小时前
06-EaEngine调度器
java·低代码·架构
天涯明月19933 小时前
Ray 架构解析——以动态任务图统一 AI 计算的分布式框架
大数据·人工智能·分布式·架构·ray
IT大白鼠3 小时前
Wazuh开源安全平台技术架构与功能特性深度解析
架构·开源xdr/siem
码域空间3 小时前
锁住了检查,锁不住延迟——MySQL 读写分离架构下双重检查模式失效实录
数据库·mysql·架构
Roadinforest4 小时前
Claude Code 架构深度解析:从 Agent Loop 到 Tool、MCP 与 Context
ai·架构·llm·agent·anthropic·claudecode
zhaodezhu16884 小时前
四维技术全域赋能 一网推重构企业数字营销增长新范式
安全·架构·泰兴geo优化·可靠公司·苏州geo技术·四维技术·全域赋能
橘色的喵4 小时前
PySide6 工业上位机的实时帧链、零拷贝与跨语言架构
c++·架构·图像·pyside
OPEN-F4 小时前
Python进阶教程:自动化办公实战
python·c#·自动化