追求极致解耦
技术这条路,即苦行僧般的修行!

导航

 

View和ViewModel的绑定

原生:

DataContext = new  xxxViewModel();

 

 

 MVVMLight:

使用NUGET安装MVVMLight包后,会自动生成ViewModel文件夹,里面包含一个ViewModelLocator,在这个文件中,使用依赖注入的方式,添加对ViewModel的注册,然后在.xaml文件中引入。这样做的好处,

1:一个view可以注册多个viewmodel,

2:从.XAML文件中引入,在界面会及时显示出绑定的数据,友好界面调整。

3:切换页面的时候,数据可以缓存的,因为在ViewModelLocator中,ServiceLocator.Current.GetInstance(class)这个方法是通过单例模式注册获取的。就是说,ViewModel类中定义的属性值是一直存在。

   如果是在.cs文件中通过  DataContext=new xxxViewModel();关联ViewModel类,每次都会new一个新的初始化的类。

 public class ViewModelLocator
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            ////if (ViewModelBase.IsInDesignModeStatic)
            ////{
            ////    // Create design time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DesignDataService>();
            ////}
            ////else
            ////{
            ////    // Create run time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DataService>();
            ////}

            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<WelcomeViewModel>();
            SimpleIoc.Default.Register<BothWayBindViewModel>();
        }

        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }

        public WelcomeViewModel Welcome
        {
            get
            {
                return ServiceLocator.Current.GetInstance<WelcomeViewModel>();
            }
        }
        public BothWayBindViewModel BothWayBind
        {
            get
            {
                return ServiceLocator.Current.GetInstance<BothWayBindViewModel>();
            }
        }


        public static void Cleanup()
        {
            // TODO Clear the ViewModels
        }
    }
View Code

 

<Window x:Class="WPF_MVVMLight.BothWayBindView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_MVVMLight"
         DataContext="{Binding Source={StaticResource Locator},Path=BothWayBind}"
        mc:Ignorable="d"
        Title="BothWayBindView" Height="450" Width="800">
    <Window.Resources>
View Code

 

数据绑定

原生:

实现INotifyPropertyChanged接口的基类

 public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Sets property if it does not equal existing value. Notifies listeners if change occurs.
        /// </summary>
        /// <typeparam name="T">Type of property.</typeparam>
        /// <param name="member">The property's backing field.</param>
        /// <param name="value">The new value.</param>
        /// <param name="propertyName">Name of the property used to notify listeners.  This
        /// value is optional and can be provided automatically when invoked from compilers
        /// that support <see cref="CallerMemberNameAttribute"/>.</param>
        protected virtual bool SetProperty<T>(ref T member, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(member, value))
            {
                return false;
            }

            member = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        /// <summary>
        /// Notifies listeners that a property value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property, used to notify listeners.</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
View Code

model绑定

 public class SelectableViewModel : ViewModelBase
    {
        private bool _isSelected;
        private string _name;
        private string _description;
        private char _code;
        private double _numeric;
        private string _food;
        private string _visibility;



        public bool IsSelected
        {
            get => _isSelected;
            set => SetProperty(ref _isSelected, value);
        }

        public string Visibility
        {
            get => _visibility;
            set => SetProperty(ref _visibility, value);
        }

        public char Code
        {
            get => _code;
            set => SetProperty(ref _code, value);
        }

        public string Name
        {
            get => _name;
            set => SetProperty(ref _name, value);
        }

        public string Description
        {
            get => _description;
            set => SetProperty(ref _description, value);
        }

        public double Numeric
        {
            get => _numeric;
            set => SetProperty(ref _numeric, value);
        }

        public string Food
        {
            get => _food;
            set => SetProperty(ref _food, value);
        }
    }
View Code

MVVMLight

调用MVVMLight的ObservableObject,自动实现INotifyPropertyChanged,在属性中添加 RaisePropertyChanged(() => “属性名”);

  public class WelcomeModel : ObservableObject
    {
        private String introduction;
        /// <summary>
        /// 欢迎词
        /// </summary>
        public String Introduction
        {
            get { return introduction; }
            set
            {
                introduction = value; RaisePropertyChanged(() => Introduction);

            }
        }
    }
View Code

注意:对于数组集合类的,一定使用ObservableCollection声明(原生和MVVMLight两者都适用)

 public ObservableCollection<MovieCategory> MovieCategories { get; }
  MovieCategories = new ObservableCollection<MovieCategory>
            {
                new MovieCategory("一号货架",
                    new Movie("一排一号", "1001"),
                    new Movie("二排一号", "1002"),
                    new Movie("三排一号", "1003")),
                new MovieCategory("二号货架",
                    new Movie("一排一号", "2001"),
                    new Movie("二排一号", "2002")
                )
            };
View Code

因为ObservableCollection实现了接口Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged,而List没有实现INotifyCollectionChanged, INotifyPropertyChanged。

  [TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
    public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        public ObservableCollection();
        //
        // 摘要:
        //     Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection`1
        //     class that contains elements copied from the specified list.
        //
        // 参数:
        //   list:
        //     The list from which the elements are copied.
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     The list parameter cannot be null.
        public ObservableCollection(List<T> list);
        public ObservableCollection(IEnumerable<T> collection);

        public event NotifyCollectionChangedEventHandler CollectionChanged;
        protected event PropertyChangedEventHandler PropertyChanged;

        public void Move(int oldIndex, int newIndex);
        protected IDisposable BlockReentrancy();
        protected void CheckReentrancy();
        protected override void ClearItems();
        protected override void InsertItem(int index, T item);
        protected virtual void MoveItem(int oldIndex, int newIndex);
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e);
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e);
        protected override void RemoveItem(int index);
        protected override void SetItem(int index, T item);
    }
View Code

 

命令

原生

实现ICommand接口

/// <summary>
    /// No WPF project is complete without it's own version of this.
    /// </summary>
    public class AnotherCommandImplementation : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;

        public AnotherCommandImplementation(Action<object> execute)
            : this(execute, null)
        { }

        public AnotherCommandImplementation(Action<object> execute, Func<object, bool> canExecute)
        {
            if (execute is null) throw new ArgumentNullException(nameof(execute));

            _execute = execute;
            _canExecute = canExecute ?? (x => true);
        }

        public bool CanExecute(object parameter) => _canExecute(parameter);

        public void Execute(object parameter) => _execute(parameter);

        public event EventHandler CanExecuteChanged
        {
            add
            {
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                CommandManager.RequerySuggested -= value;
            }
        }

        public void Refresh() => CommandManager.InvalidateRequerySuggested();
    }
View Code
命令调用
 public AnotherCommandImplementation AddCommand { get; }

        public AnotherCommandImplementation RemoveSelectedItemCommand { get; }


            AddCommand = new AnotherCommandImplementation(
                _ =>
                {
                    if (!MovieCategories.Any())
                    {
                        MovieCategories.Add(new MovieCategory(GenerateString(15)));
                    }
                    else
                    {
                        var index = new Random().Next(0, MovieCategories.Count);

                        MovieCategories[index].Movies.Add(
                            new Movie(GenerateString(15), GenerateString(20)));
                    }
                });

            RemoveSelectedItemCommand = new AnotherCommandImplementation(
                _ =>
                {
                    var movieCategory = SelectedItem as MovieCategory;
                    if (movieCategory != null)
                    {
                        MovieCategories.Remove(movieCategory);
                    }
                    else
                    {
                        var movie = SelectedItem as Movie;
                        if (movie == null) return;
                        MovieCategories.FirstOrDefault(v => v.Movies.Contains(movie))?.Movies.Remove(movie);
                    }
                },
                _ => SelectedItem != null);
View Code

 

MVVMLight

 

 

 

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using MVVMLightDemo.Model;
using System.Collections.ObjectModel;

namespace MVVMLightDemo.ViewModel
{
    public class CommandViewModel:ViewModelBase
    {
        public CommandViewModel()
        {
            //构造函数
            ValidateUI = new ValidateUserInfo();
            List = new ObservableCollection<ValidateUserInfo>();
        }

        #region 全局属性
        private ObservableCollection<ValidateUserInfo> list;
        /// <summary>
        /// 用户数据列表
        /// </summary>
        public ObservableCollection<ValidateUserInfo> List
        {
            get { return list; }
            set { list = value; }
        }

        private ValidateUserInfo validateUI;
        /// <summary>
        /// 当前操作的用户信息
        /// </summary>
        public ValidateUserInfo ValidateUI
        {
            get { return validateUI; }
            set
            {
                validateUI = value;
                RaisePropertyChanged(() => ValidateUI);
            }
        }
        #endregion

        #region 全局命令
        private RelayCommand submitCmd;
        /// <summary>
        /// 执行提交命令的方法
        /// </summary>
        public RelayCommand SubmitCmd
        {
            get
            {
                if (submitCmd == null) return new RelayCommand(() => ExcuteValidForm(),CanExcute);
                return submitCmd;
            }
            set { submitCmd = value; }
        }
        #endregion

        #region 附属方法
        /// <summary>
        /// 执行提交方法
        /// </summary>
        private void ExcuteValidForm()
        {
            List.Add(new ValidateUserInfo(){ UserEmail= ValidateUI.UserEmail, UserName = ValidateUI.UserName, UserPhone = ValidateUI.UserPhone });
        }

        /// <summary>
        /// 是否可执行(这边用表单是否验证通过来判断命令是否执行)
        /// </summary>
        /// <returns></returns>
        private bool CanExcute()
        {
            return ValidateUI.IsValidated;
        }
        #endregion

    }
}
View Code

 

注意:CanExecuteChanged,CanExecute,Execute的说明

 

 

 Messenger消息注册

如果需要某张视图页面弹出对话框、弹出子窗体、处理界面元素,播放动画等。如果这些操作都放在ViewModel中,就会导致ViewModel还是要去处理View级别的元素,造成View和ViewModel的依赖。

最好的办法就是ViewModel通知View应该做什么,而View监听接收到命令,并去处理这些界面需要处理的事情。

      2、ViewModel和ViewModel之间也需要通过消息传递来完成一些交互。 

      而MVVM Light  的 Messenger类,提供了解决了上述两个问题的能力:

Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send<TMessage, TTarget>(TMessage message) 实现,

 

 

public partial class NessagerForView : Window
    {
        public NessagerForView()
        {
            InitializeComponent();

            //消息标志token:ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致
            //执行方法Action:ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。
            Messenger.Default.Register<String>(this, "ViewAlert", ShowReceiveInfo);
            this.DataContext = new MessengerRegisterForVViewModel();
            //卸载当前(this)对象注册的所有MVVMLight消息
            this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
        }

        /// <summary>
        /// 接收到消息后的后续工作:根据返回来的信息弹出消息框
        /// </summary>
        /// <param name="msg"></param>
        private void ShowReceiveInfo(String msg)
        {
            MessageBox.Show(msg);
        }
    }
View Code

 

public class MessengerRegisterForVViewModel:ViewModelBase
    {

        public MessengerRegisterForVViewModel()
        {

        }

        #region 命令

        private RelayCommand sendCommand;
        /// <summary>
        /// 发送命令
        /// </summary>
        public RelayCommand SendCommand
        {
            get
            {
                if (sendCommand == null)
                    sendCommand = new RelayCommand(() => ExcuteSendCommand());
                return sendCommand;

            }
            set { sendCommand = value; }
        }

        private void ExcuteSendCommand()
        {
            Messenger.Default.Send<String>("ViewModel通知View弹出消息框", "ViewAlert"); //注意:token参数一致
        }

        #endregion
    }
View Code

比如弹出框,可以在ViewMode中使用,但是需要引入using System.Windows;如果是子页面,就脱了了View和ViewModel解耦的初衷。

 

posted on 2021-06-11 13:09  追求极致解构  阅读(673)  评论(0)    收藏  举报