You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WPF MVVM模式技术咨询:新手转型疑问

从直接写数据库代码到MVVM:WPF新手的入门改造指南

嘿,作为刚入门编程的WPF新手,没用地设计模式直接写数据库代码太正常啦——我当初刚接触WPF的时候也这么干过,把所有逻辑都堆在后台代码里,后来维护起来简直头大😂。咱们一步步把你原来的代码改造成符合MVVM模式的结构,你就能明白为啥这种模式更适合WPF了。

先说说你原来写法的问题

你之前直接在UI后台写SqlConnection、SqlCommand这类代码,会导致:

  • UI和数据逻辑高度耦合:改个按钮样式或者数据库连接字符串,都得翻同一堆代码
  • 不好测试:想验证数据库查询是否正确,必须启动整个WPF应用,没法单独测试逻辑
  • 代码乱糟糟:业务逻辑、UI事件、数据库操作混在一起,后期加功能越写越乱

MVVM的核心思路:分层解耦

MVVM把代码分成三个核心层:

  • Model(模型):对应数据库里的实体(比如你的Product),以及数据访问逻辑(和数据库打交道的代码)
  • ViewModel(视图模型):作为UI和Model的中间层,处理业务逻辑,给UI提供可绑定的数据和命令
  • View(视图):就是你的XAML界面,只负责展示数据和响应用户操作,不写业务逻辑

一步步改造你的代码

1. 先写Model层:封装数据库操作和实体

首先把数据库操作抽成单独的类,再定义Product实体:

// 数据访问类:专门处理和数据库的交互
public class ProductDataAccess
{
    // 把连接字符串抽出来,方便后期修改
    private readonly string _connectionString = "Server = localhost;Database = Bilanc; Integrated Security = true";

    // 调用存储过程获取产品列表的方法
    public List<Product> GetProductsFromDb()
    {
        var productList = new List<Product>();
        
        // 用using自动释放资源,避免内存泄漏
        using (SqlConnection con = new SqlConnection(_connectionString))
        {
            using (SqlCommand cmd = new SqlCommand("Product", con))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                con.Open();
                
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        // 把数据库读取到的字段映射到Product实体
                        var product = new Product
                        {
                            Id = reader.GetInt32(reader.GetOrdinal("Id")),
                            Name = reader.GetString(reader.GetOrdinal("Name")),
                            // 这里根据你的存储过程返回的字段,补充其他属性比如Price、Stock等
                        };
                        productList.Add(product);
                    }
                }
            }
        }
        return productList;
    }
}

// Product实体类:对应数据库里的产品表结构
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    // 按需添加其他属性:public decimal Price { get; set; } 之类的
}

2. 再写ViewModel层:连接UI和数据

ViewModel要实现INotifyPropertyChanged接口,让UI能自动响应数据变化;还要定义命令,让UI按钮能触发加载数据的逻辑:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

public class ProductViewModel : INotifyPropertyChanged
{
    private readonly ProductDataAccess _dataAccess;
    // 用ObservableCollection代替List,这样数据变化时UI会自动更新
    public ObservableCollection<Product> Products { get; set; }

    // 定义加载产品的命令,绑定到UI按钮
    public ICommand LoadProductsCommand { get; }

    public ProductViewModel()
    {
        _dataAccess = new ProductDataAccess();
        Products = new ObservableCollection<Product>();
        // 初始化命令,关联到加载数据的方法
        LoadProductsCommand = new RelayCommand(LoadProducts);
    }

    // 实际加载数据的方法
    private void LoadProducts()
    {
        var products = _dataAccess.GetProductsFromDb();
        Products.Clear();
        foreach (var product in products)
        {
            Products.Add(product);
        }
    }

    // 实现INotifyPropertyChanged接口,通知UI数据变化
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// 通用的RelayCommand实现:WPF里用来绑定UI命令的常用类
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute();
    }

    public void Execute(object parameter)
    {
        _execute();
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

3. 最后写View层:纯XAML绑定

你的XAML界面只需要绑定ViewModel的属性和命令,不用写任何后台逻辑:

<Window x:Class="BilancApp.ProductWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:BilancApp"
        Title="产品列表" Height="450" Width="800">
    <!-- 设置DataContext为ViewModel,让UI能找到绑定的数据源 -->
    <Window.DataContext>
        <local:ProductViewModel />
    </Window.DataContext>
    <StackPanel Margin="10">
        <!-- 按钮绑定LoadProductsCommand,点击就会触发ViewModel里的LoadProducts方法 -->
        <Button Content="加载产品" Command="{Binding LoadProductsCommand}" Margin="0 0 0 10"/>
        <!-- DataGrid绑定Products集合,自动展示产品数据 -->
        <DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="True"/>
    </StackPanel>
</Window>

这么改造的好处

  • 代码更整洁:每个层只干自己的事,找问题、加功能都更方便
  • 可测试性强:可以单独写测试用例,直接调用ViewModel的LoadProducts方法,不用启动UI就能验证逻辑
  • UI和逻辑分离:设计师改XAML界面,程序员改ViewModel逻辑,互不干扰
  • 符合WPF的设计理念:充分利用数据绑定,不用手动去更新UI控件的内容

刚开始可能觉得MVVM有点繁琐,但写多了之后会发现它能帮你省很多麻烦,尤其是开发复杂的WPF应用时。如果觉得手动写RelayCommand麻烦,后期也可以用MvvmLight、Prism这类MVVM框架来简化开发~

内容的提问来源于stack exchange,提问作者lex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:40:07