zoukankan      html  css  js  c++  java
  • 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之间的通信

  • 相关阅读:
    Angularjs乱记
    tornado code
    angularjs中templateUrl的路径问题
    bat脚本禁用和开启本地连接
    http-server使用
    angularjs loading, animate
    tornado输出json
    cmder切换路径、设置命令别名
    python __setattr__和__getattr__
    滚动加载
  • 原文地址:https://www.cnblogs.com/xietianjiao/p/13386859.html
Copyright © 2011-2022 走看看