WPF之再谈MVVM

MVVM简介

MVVM模式由Model,View,ViewModel三部分组成。

Model需继承INotifyPropertyChange(属性修改通知)

ViewModel负责业务逻辑,连接View和Model

View上面的控件绑定model和命令(command)

注:数据绑定binding实现了INotifyPropertyChange接口的事件。

MVVM框架实现了数据双向绑定,即View和Model双向绑定。最终实现包含Model,Command,View,ViewModel四部分。

问题的关键

关键是要能准确的进行ViewModel的建模,处理好View与ViewModel之间的关系

只有2种关系:

               数据传递 --- 双向,使用Binding实现;

               操作传递 --- 单向(只从View传递给ViewModel),使用命令Command实现;

数据绑定

1、创建NotificationObject

首先创建NotificationObject,它是所以ViewModel的基类

         因为要使用Binding,而ViewModel就充当数据源的角色,而要实现当值有变化时会自动响应,就必须实现INotifyPropertyChanged接口,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMTest.ViewModels
{
    public class NotificationObject:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string property)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));

        }
    }
}

 

2、创建ViewModel

using MVVMTest.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMTest.ViewModels
{
    public class MainWindowViewModel:NotificationObject
    {
        private string txt1;

        public string Txt1
        {
            get { return txt1; }
            set
            {
                txt1 = value;
                this.RaisePropertyChanged("Txt1");
            }
        }

        private string txt2;

        public string Txt2
        {
            get { return txt2; }
            set
            {
                txt2 = value;
                this.RaisePropertyChanged("Txt2");
            }
        }

        private string result;

        public string Result
        {
            get { return result; }
            set
            {
                result = value;
                this.RaisePropertyChanged("Result");
            }
        }

        public DelegateCommand ConcatCommand { get; set; }
        public void Concat(object parameter)
        {
            Result = Txt1 + " and " + Txt2;
        }


        public MainWindowViewModel()
        {
            ConcatCommand = new DelegateCommand();
            ConcatCommand.ExecuteAction = new Action<object>(Concat);
        }



    }
}

然后所有数据类型都实现NotificationObject这个类

最后:

DataContext = new MainWindowViewModel();

这样就能实现数据的绑定

命令绑定

命令绑定要关注的核心就是两个方面的问题,命令能否执行和命令怎么执行。也就是说当View中的一个Button绑定了ViewModel中一个命令后,什么时候这个Button是可用的,按下Button后执行什么操作。解决了这两个问题基本就实现了命令绑定。另外一个问题就是执行过程中需要的数据(参数)要如何传递。

自定义一个能够被绑定的命令需要实现ICommand接口。该接口包含:

public event EventHandler CanExecuteChanged // 在命令可执行状态发生改变时触发

public bool CanExecute(object parameter) //检查命令是否可用的方法

public void Execute(object parameter)  //命令执行的方法

创建一个类(当然也可以创建支持泛型的命令)

    public class MyCommand : ICommand
    {
        public bool CanExecute(object parameter)
        {
            return true;   //表示是否执行下面那个Execute方法.
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)   //这里是定义按钮按下去,需要执行的内容
        {
            MessageBox.Show("我这里是定义死了,你可以通过传值的方法,来自定义显示的内容.");
        }

    }

在ViewModel中创建命令属性

    public class ViewModel:NotificationObject
    {
        public ICommand MyCmd
        {
            get
            {

                return new MyCommand();
            }
        }
    }
DataContext = new ViewModel();  //将viewModel这个实例绑定到当前页面的D数据上下文上!前边实现了的

最后界面绑定

<Button Content="ShowMsg"  Command="{Binding MyCmd}" Height="158" Margin="91,244,106,0" Name="button1" VerticalAlignment="Top" />

事件绑定

为什么要用事件绑定?这个问题其实是很好理解的,因为事件是丰富多样的,单纯的命令绑定远不能覆盖所有的事件。例如Button的命令绑定能够解决Click事件的需求,但Button的MouseEnter、窗体的Loaded等大量的事件要怎么处理呢?这就用到了事件绑定。

方法一:重写InvokeCommandAction来扩充返回的参数

public class EventToCommand : TriggerAction<DependencyObject>
    {
        private string commandName;
        public readonly static DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommand), null);
        public readonly static DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventToCommand), new PropertyMetadata(null, (DependencyObject s, DependencyPropertyChangedEventArgs e) =>
        {
            EventToCommand sender = s as EventToCommand;
            if (sender == null)
            {
                return;
            }
            if (sender.AssociatedObject == null)
            {
                return;
            }
        }));
 
        /// <summary>
        /// 获取或设置此操作应调用的命令。这是依赖属性。
        /// </summary>
        /// <value>要执行的命令。</value>
        /// <remarks>如果设置了此属性和 CommandName 属性,则此属性将优先于后者。</remarks>
        public ICommand Command
        {
            get
            {
                return (ICommand)base.GetValue(EventToCommand.CommandProperty);
            }
            set
            {
                base.SetValue(EventToCommand.CommandProperty, value);
            }
        }
 
        /// <summary>
        /// 获得或设置命令参数。这是依赖属性。
        /// </summary>
        /// <value>命令参数。</value>
        /// <remarks>这是传递给 ICommand.CanExecute 和 ICommand.Execute 的值。</remarks>
        public object CommandParameter
        {
            get
            {
                return base.GetValue(EventToCommand.CommandParameterProperty);
            }
            set
            {
                base.SetValue(EventToCommand.CommandParameterProperty, value);
            }
        }
 
        /// <summary>
        /// 获得或设置此操作应调用的命令的名称。
        /// </summary>
        /// <value>此操作应调用的命令的名称。</value>
        /// <remarks>如果设置了此属性和 Command 属性,则此属性将被后者所取代。</remarks>
        public string CommandName
        {
            get
            {
                base.ReadPreamble();
                return this.commandName;
            }
            set
            {
                if (this.CommandName != value)
                {
                    base.WritePreamble();
                    this.commandName = value;
                    base.WritePostscript();
                }
            }
        }
 
        /// <summary>
        /// 调用操作。
        /// </summary>
        /// <param name="parameter">操作的参数。如果操作不需要参数,则可以将参数设置为空引用。</param>
        protected override void Invoke(object parameter)
        {
            if (base.AssociatedObject == null)
                return;
            ICommand command = this.ResolveCommand();
 
            /*
             * ★★★★★★★★★★★★★★★★★★★★★★★★
             * 注意这里添加了事件触发源和事件参数
             * ★★★★★★★★★★★★★★★★★★★★★★★★
             */
            ExCommandParameter exParameter = new ExCommandParameter
            {
                Sender = base.AssociatedObject,
                //Parameter = GetValue(CommandParameterProperty),
                Parameter = this.CommandParameter,
                EventArgs = parameter as EventArgs
            };
 
            if (command != null && command.CanExecute(exParameter))
            {
                /*
                 * ★★★★★★★★★★★★★★★★★★★★★★★★
                 * 注意将扩展的参数传递到Execute方法中
                 * ★★★★★★★★★★★★★★★★★★★★★★★★
                 */
                command.Execute(exParameter);
            }
        }
 
        private ICommand ResolveCommand()
        {
            if (this.Command != null)
                return this.Command;
            if (base.AssociatedObject == null)
                return null;
            ICommand result = null;
            Type type = base.AssociatedObject.GetType();
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            for (int i = 0; i < properties.Length; i++)
            {
                PropertyInfo propertyInfo = properties[i];
                if (typeof(ICommand).IsAssignableFrom(propertyInfo.PropertyType) && string.Equals(propertyInfo.Name, this.CommandName, StringComparison.Ordinal))
                {
                    result = (ICommand)propertyInfo.GetValue(base.AssociatedObject, null);
                    break;
                }
            }
            return result;
        }
    }

其中:EventToCommand 类是自定义的扩充类

 public class ExCommandParameter
    {
        /// <summary>
        /// 事件触发源
        /// </summary>
        public DependencyObject Sender { get; set; }
        /// <summary>
        /// 事件参数
        /// </summary>
        public EventArgs EventArgs { get; set; }
        /// <summary>
        /// 额外参数
        /// </summary>
        public object Parameter { get; set; }
 

}

引入xaml命令空间

xmlns:loc="clr-namespace:WpfProgect.Base"

然后就可以调用了,如下: 

<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<!--★★★扩展的InvokeCommandAction★★★-->
<loc:EventToCommand Command="{Binding StretchSelectionChangedCommand}" 
CommandParameter ="{Binding ElementName=sampleViewBox}"/>
</i:EventTrigger>

后台:

首先需要在ViewModel里进行命令绑定的初始化,如:

StretchSelectionChangedCommand = new DelegateCommand() 
{ ExecuteActionObj = new Action<object>(StretchSelectionChanged) };

 


当然,具体实现方式要根据自己编写的DelegateCommand类来决定。

绑定的StretchSelectionChanged方法实现如下:

private void StretchSelectionChanged(object obj)
{
ComboBox cbStretch = ((ExCommandParameter)obj).Sender as ComboBox;
Viewbox sampleViewBox = ((ExCommandParameter)obj).Parameter as Viewbox;
if (cbStretch.SelectedItem != null)
{
sampleViewBox.Stretch = uiModel.StretchMode;
}
}

 

方法二:运用Behavior来实现事件,再运用视图树VisualTree来找所需的父控件或者子控件(控件到手了,就可以取到所需的参数),或者通过写扩展属性的方式来获取控件,以下Demo是通过写扩展属性来实现的。

xaml调用方式如下:

<Slider x:Name="HSlider" Minimum="0" Maximum="100" Height="24" Margin="79,0,91,42" VerticalAlignment="Bottom" Width="150">
<i:Interaction.Behaviors>
<behav:SliderBehavior TargetGrid="{Binding ElementName=theContainer}" 
TargetViewBox="{Binding ElementName=sampleViewBox}"/>
</i:Interaction.Behaviors>
</Slider>

 


SliderBehavior类如下:

class SliderBehavior : Behavior<Slider>
{
public readonly static DependencyProperty TargetGridProperty = DependencyProperty.Register("TargetGrid", typeof(Grid), typeof(SliderBehavior), null);
public readonly static DependencyProperty TargetViewBoxProperty = DependencyProperty.Register("TargetViewBox", typeof(Viewbox), typeof(SliderBehavior), null);
/// <summary>
/// 获得或设置命令参数。这是依赖属性。
/// </summary>
/// <value>命令参数。</value>
/// <remarks>这是传递给 ICommand.CanExecute 和 ICommand.Execute 的值。</remarks>
public Grid TargetGrid
{
get
{
return (Grid)base.GetValue(SliderBehavior.TargetGridProperty);
}
set
{
base.SetValue(SliderBehavior.TargetGridProperty, value);
}
}
public Viewbox TargetViewBox
{
get
{
return (Viewbox)base.GetValue(SliderBehavior.TargetViewBoxProperty);
}
set
{
base.SetValue(SliderBehavior.TargetViewBoxProperty, value);
}
}

protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.ValueChanged += new RoutedPropertyChangedEventHandler<double>(HSlider_ValueChanged);
}

protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.ValueChanged -= HSlider_ValueChanged;
}

void HSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (this.AssociatedObject.Name == "HSlider")
TargetViewBox.Width = TargetGrid.ActualWidth * this.AssociatedObject.Value / 100.0;
else TargetViewBox.Height = TargetGrid.ActualHeight * this.AssociatedObject.Value / 100.0;
}
}

View和ViemModel通信

MVVMLight实现了一套略有复杂的消息通信,包含了定类型发送、分组发送、发送给包含继承类型的目标、广播等。

ViemModel和ViemModel之间的通信

 

posted @ 2020-07-27 18:12  卖雨伞的小男孩  阅读(654)  评论(0编辑  收藏  举报