少有人走的路

勇哥的工业自动化技术网站

wpf快速了解(3)MVVM模式,行为绑定

勇哥注:

此系列贴子服务于C# winform开发工程师,帮助他们快速了解wpf技术,节省大家的学习成本。

因此,这个系列不是讲给初学者听的。

我们知道winform是C#的标准ui框架,wpf则是另一种ui框架,随着.net Core的流行,它会是今后的主流选择,我们必须学习它。


系列贴子导航:

wpf快速了解(1)基础开发环境

wpf快速了解(2)事件驱动和数据驱动

wpf快速了解(3)MVVM模式,行为绑定

wpf快速了解(4)行为绑定,后续

wpf快速了解(5)数据集合的绑定


MVVM模式,它是三个词的缩写(Model, View, ViewModel),意义如下:


MVVM模式把桌面软件的开发分为上面说的三个层面(Model, View, ViewModel),

最主要的是把View和ViewModel进行解耦,以达到界面与业务逻辑的分离。

而Model和ViewModel是一个强关联的关系,这一点,通过本篇文章你就会看得到。

Model可以有许多个(因为复杂界面的数据更新是按功能进行分块的),这样ViewModel中可以有多个Model。


勇哥2026.1.19注,划线部分是错误的,正确如下:


Model

纯数据载体(所有核心数据实体),无业务逻辑、无界面关联,是程序的 “数据地基”;ViewModel: 核心职责:封装业务逻辑 + 数据适配 + 作为 View 和 Model 的中间人; 与 View 的关系:基于 “绑定契约” 解耦,双向交互(View→ViewModel:用户操作触发命令 / 属性变更;ViewModel→View:属性变更通知界面更新); 与 Model 的关系:直接引用 Model,操作 / 加工 Model 的数据;View

纯展示层,仅通过绑定契约对接 ViewModel,无业务逻辑、无 Model 引用,只负责 “看” 和 “收用户操作”。

关键点回顾Model 的核心是 “数据载体”,和 “业务逻辑是否存在” 无关; ViewModel 的核心是 “逻辑 + 适配 + 中间层”,不只是 “联络人”,还是业务逻辑的唯一执行载体; View 和 ViewModel 的解耦是 “绑定契约解耦”,双向交互是核心特征。


Model 可以有多个,但拆分依据是业务领域(比如用户、订单、商品是不同业务实体),而非 “界面功能分块”;

ViewModel 中可以 “依赖” 多个 Model,但需遵循 “职责单一”—— 一个 ViewModel 通常对应一个业务场景(比如订单 ViewModel 只处理订单相关),且不会直接处理多 Model 的交互(而是通过 Service 层封装)。



通过上篇贴子《wpf快速了解(2)事件驱动和数据驱动》完成了数据驱动的演示后,该代码离MVVM模式比较接近了。

本篇完成后,基本上就能看到MVVM模式的样子了。


勇哥说下本篇的新需求:

仍然实现上篇的功能,但是要求按钮事件里完全没有代码,即下面的代码都没有了。

private void ButRead_Click(object sender, RoutedEventArgs e)
        {
            model.Value = "100";
        }

这样整个界面后台代码只存在一句 this.DataContext = model;

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = model;
        }



我们先在MainViewModel中添加一个属性。

 private ICommand myCommandValue;

 public ICommand MyCommandValue
 {
    get { return myCommandValue; }
    set { myCommandValue = value; }
 }



把“读”按钮的Click删除掉,换成Command

这个Command实际还是Click事件,但是它可以绑定对象属性MyCommandValue,

点击按钮后会执行这个属性的代码。

image.png

下面的 {Binding MyCommandValue} 即是本文的主题“行为绑定”。

<Button Name="ButRead" Content="读" HorizontalAlignment="Left" Margin="22,173,0,0" VerticalAlignment="Top" Height="24" Width="78" Grid.Column="1" 
  Command="{Binding MyCommandValue}"
 />


由于上面的属性MyCommandValue,它的类型是一个接口ICommand,所以我们得做一个实现类。

这个实现类的默认内容如下:

两个方法,一个事件,啥用处?继续看。。。

    internal class CommandValue : ICommand
    {
        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            throw new NotImplementedException();
        }

        public void Execute(object? parameter)
        {
            throw new NotImplementedException();
        }
    }

回到MainViewModel的MyCommandValue属性定义。

这里我们new一个CommandValue做为myCommandValue的初始值。

 private ICommand myCommandValue=new CommandValue();

        public ICommand MyCommandValue
        {
            get { return myCommandValue; }
            set { myCommandValue = value; }
        }


好了,我们来研究第一个方法Execute,它是带参数的。

这个参数调用者可以不传,但是如果要专的话,要在xaml里面加标签。

 public void Execute(object? parameter)
        {
            throw new NotImplementedException();
        }

如下:

添加属性 CommandParameter传入参数。执行后字符串"hackpig"会传入函数Excute

<Button Name="ButRead" Content="读" HorizontalAlignment="Left" Margin="22,173,0,0" VerticalAlignment="Top" Height="24" Width="78" Grid.Column="1" 
                Command="{Binding MyCommandValue}"
                CommandParameter="hackpig"
/>


我们来完成函数Excute的功能,但是出现下面注释的问题。

怎么办?

        public void Execute(object? parameter)
        {
            //控制逻辑
            //此处原意是想写 Value=100,但是此处访问不到Value

        }


这时候我们可以定义一个委托,然后在后面再让ICommand接口的 MyCommandValue属性去执行这个委托。

如下:

    internal class CommandValue : ICommand
    {
        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            return true;
        }

        public void Execute(object? parameter)
        {
            //控制逻辑
            //此处原意是想写 Value=100,但是此处访问不到Value
            OnAction?.Invoke(parameter);
        }

        public Action<object?> OnAction { get; set; }
        
    }

MyCommandValue属性这边我们这样写:

       private ICommand myCommandValue;

        public ICommand MyCommandValue
        {
            get 
            {
                if (myCommandValue == null)
                {
                    myCommandValue = new CommandValue()
                    {
                       OnAction = new Action<object?>(Action1)
                    };
                }
                return myCommandValue;
            }
            set { myCommandValue= value; }
        }

        private void Action1(object? para)
        {
            Value= "100";
        }


至此,勇哥已经完成了本例的要求,如下图所示:

我们在UI的后台代码中已经看到了跟业务逻辑有关的任何代码了。

就只剩下那一句“this.DataContext = model;”

image.png


至此,我们的MVVM模式已经基本成型了。




本文源码下载:

支付2元或购买VIP会员后,才能查看本内容!立即支付升级会员


--------------------- 

作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!



发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«    2026年2月    »
1
2345678
9101112131415
16171819202122
232425262728
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接

Powered By Z-BlogPHP 1.7.3

Copyright www.skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864