博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF自定义控件与样式(14)-轻量MVVM模式实践
阅读量:7078 次
发布时间:2019-06-28

本文共 18804 字,大约阅读时间需要 62 分钟。

原文:

一.前言

  申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。

  MVVM是WPF中一个非常实用的编程模式,充分利用了WPF的绑定机制,体现了WPF数据驱动的优势。

 

  关于MVVM网上很多介绍或者示例,本文不多做介绍了,本文的主要目的是提供一个轻量级的View Model实现,本文的主要内容:

  • 依赖通知InotifyPropertyChanged实现;
  • 命令Icommand的实现;
  • 消息的实现;
  • 一个简单MVVM示例;

  对于是否要使用MVVM、如何使用,个人觉得根据具体需求可以灵活处理,不用纠结于模式本身。用了MVVM,后置*.cs文件就不一定不允许写任何代码,混合着用也是没有问题的, 只要自己决的方便、代码结构清晰、维护方便即可。

二.依赖通知InotifyPropertyChanged实现

  依赖通知InotifyPropertyChanged是很简单的一个接口,是View Model标配的接口,一个典型的实现(BaseNotifyPropertyChanged):  

///     /// 实现了属性更改通知的基类    ///     public class BaseNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged    {        ///         /// 属性值变化时发生        ///         ///         protected virtual void OnPropertyChanged(string propertyName)        {            if (this.PropertyChanged != null)                this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));        }        public virtual event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;    }

  然后使用方式就是这样的:  

public int _Age;        public int Age        {            get { return this._Age; }            set { this._Age = value; base.OnPropertyChanged("Age"); }        }

  上面的代码有硬编码,有代码洁癖的人就不爽了,因此网上有多种解决方式,比如这篇:。本文的实现方式如下,使用表达式树:

///         /// 属性值变化时发生        ///         ///         protected virtual void OnPropertyChanged
(Expression
> propertyExpression) { var propertyName = (propertyExpression.Body as MemberExpression).Member.Name; this.OnPropertyChanged(propertyName); }

  使用上避免了硬编码,使用示例:  

public string _Name;        public string Name        {            get { return this._Name; }            set { this._Name = value; base.OnPropertyChanged(() => this.Name); }        }

三.命令Icommand的实现

  命令的实现也很简单,实现Icommand的几个接口就OK了, 考虑到使用时能更加方便,无参数RelayCommand实现:  

///     /// 广播命令:基本ICommand实现接口    ///     public class RelayCommand : ICommand    {        public Action ExecuteCommand { get; private set; }        public Func
CanExecuteCommand { get; private set; } public RelayCommand(Action executeCommand, Func
canExecuteCommand) { this.ExecuteCommand = executeCommand; this.CanExecuteCommand = canExecuteCommand; } public RelayCommand(Action executeCommand) : this(executeCommand, null) { } ///
/// 定义在调用此命令时调用的方法。 /// ///
此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。 public void Execute(object parameter) { if (this.ExecuteCommand != null) this.ExecuteCommand(); } ///
/// 定义用于确定此命令是否可以在其当前状态下执行的方法。 /// ///
/// 如果可以执行此命令,则为 true;否则为 false。 ///
///
此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。 public bool CanExecute(object parameter) { return CanExecuteCommand == null || CanExecuteCommand(); } public event EventHandler CanExecuteChanged { add { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested += value; } remove { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested -= value; } } }

  泛型参数RelayCommand<T>的版本:  

///     /// 广播命令:基本ICommand实现接口,带参数    ///     public class RelayCommand
: ICommand { public Action
ExecuteCommand { get; private set; } public Predicate
CanExecuteCommand { get; private set; } public RelayCommand(Action
executeCommand, Predicate
canExecuteCommand) { this.ExecuteCommand = executeCommand; this.CanExecuteCommand = canExecuteCommand; } public RelayCommand(Action
executeCommand) : this(executeCommand, null) { } ///
/// 定义在调用此命令时调用的方法。 /// ///
此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。 public void Execute(object parameter) { if (this.ExecuteCommand != null) this.ExecuteCommand((T)parameter); } ///
/// 定义用于确定此命令是否可以在其当前状态下执行的方法。 /// ///
/// 如果可以执行此命令,则为 true;否则为 false。 ///
///
此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。 public bool CanExecute(object parameter) { return CanExecuteCommand == null || CanExecuteCommand((T)parameter); } public event EventHandler CanExecuteChanged { add { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested += value; } remove { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested -= value; } } }

  带参数和不带参数的命令XAML绑定方式:  

ShowUser
SetName

  上面是针对提供Command模式的控件示例, 但对于其他事件呢,比如MouseOver如何绑定呢?可以借用System.Windows.Interactivity.dll,其中的 Interaction 可以帮助我们实现对命令的绑定,这是在微软Blend中提供的。添加dll应用,然后添加命名空间:

  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

 

四.消息的实现

  消息类Messenger主要目的是实现View与View Model及各个模块之间的通信。本文的消息类Messenger,参考自网络开源的实现(MVVMFoundation)。实现了松散耦合的消息通知机制,对于消息传输参数,内部使用了弱引用(WeakReference),以防止内存泄漏代码:  

///     /// Provides loosely-coupled messaging between various colleague objects.  All references to objects are stored weakly, to prevent memory leaks.    /// 提供松散耦合的消息通知机制,为防止内存泄漏,所有对象都使用了弱引用(WeakReference)    ///     public class Messenger    {        #region Constructor        public Messenger()        {        }        #endregion // Constructor        #region Register        ///         /// Registers a callback method, with no parameter, to be invoked when a specific message is broadcasted.        /// 注册消息监听        ///         /// The message to register for.        /// The callback to be called when this message is broadcasted.        public void Register(string message, Action callback)        {            this.Register(message, callback, null);        }        ///         /// Registers a callback method, with a parameter, to be invoked when a specific message is broadcasted.        /// 注册消息监听        ///         /// The message to register for.        /// The callback to be called when this message is broadcasted.        public void Register
(string message, Action
callback) { this.Register(message, callback, typeof(T)); } void Register(string message, Delegate callback, Type parameterType) { if (String.IsNullOrEmpty(message)) throw new ArgumentException("'message' cannot be null or empty."); if (callback == null) throw new ArgumentNullException("callback"); this.VerifyParameterType(message, parameterType); _messageToActionsMap.AddAction(message, callback.Target, callback.Method, parameterType); } [Conditional("DEBUG")] void VerifyParameterType(string message, Type parameterType) { Type previouslyRegisteredParameterType = null; if (_messageToActionsMap.TryGetParameterType(message, out previouslyRegisteredParameterType)) { if (previouslyRegisteredParameterType != null && parameterType != null) { if (!previouslyRegisteredParameterType.Equals(parameterType)) throw new InvalidOperationException(string.Format( "The registered action's parameter type is inconsistent with the previously registered actions for message '{0}'.\nExpected: {1}\nAdding: {2}", message, previouslyRegisteredParameterType.FullName, parameterType.FullName)); } else { // One, or both, of previouslyRegisteredParameterType or callbackParameterType are null. if (previouslyRegisteredParameterType != parameterType) // not both null? { throw new TargetParameterCountException(string.Format( "The registered action has a number of parameters inconsistent with the previously registered actions for message \"{0}\".\nExpected: {1}\nAdding: {2}", message, previouslyRegisteredParameterType == null ? 0 : 1, parameterType == null ? 0 : 1)); } } } } #endregion // Register #region Notify ///
/// Notifies all registered parties that a message is being broadcasted. /// 发送消息通知,触发监听执行 /// ///
The message to broadcast. ///
The parameter to pass together with the message. public void Notify(string message, object parameter) { if (String.IsNullOrEmpty(message)) throw new ArgumentException("'message' cannot be null or empty."); Type registeredParameterType; if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType)) { if (registeredParameterType == null) throw new TargetParameterCountException(string.Format("Cannot pass a parameter with message '{0}'. Registered action(s) expect no parameter.", message)); } var actions = _messageToActionsMap.GetActions(message); if (actions != null) actions.ForEach(action => action.DynamicInvoke(parameter)); } ///
/// Notifies all registered parties that a message is being broadcasted. /// 发送消息通知,触发监听执行 /// ///
The message to broadcast. public void Notify(string message) { if (String.IsNullOrEmpty(message)) throw new ArgumentException("'message' cannot be null or empty."); Type registeredParameterType; if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType)) { if (registeredParameterType != null) throw new TargetParameterCountException(string.Format("Must pass a parameter of type {0} with this message. Registered action(s) expect it.", registeredParameterType.FullName)); } var actions = _messageToActionsMap.GetActions(message); if (actions != null) actions.ForEach(action => action.DynamicInvoke()); } #endregion // NotifyColleauges #region MessageToActionsMap [nested class] ///
/// This class is an implementation detail of the Messenger class. /// private class MessageToActionsMap { #region Constructor internal MessageToActionsMap() { } #endregion // Constructor #region AddAction ///
/// Adds an action to the list. /// ///
The message to register. ///
The target object to invoke, or null. ///
The method to invoke. ///
The type of the Action delegate. internal void AddAction(string message, object target, MethodInfo method, Type actionType) { if (message == null) throw new ArgumentNullException("message"); if (method == null) throw new ArgumentNullException("method"); lock (_map) { if (!_map.ContainsKey(message)) _map[message] = new List
(); _map[message].Add(new WeakAction(target, method, actionType)); } } #endregion // AddAction #region GetActions ///
/// Gets the list of actions to be invoked for the specified message /// ///
The message to get the actions for ///
Returns a list of actions that are registered to the specified message
internal List
GetActions(string message) { if (message == null) throw new ArgumentNullException("message"); List
actions; lock (_map) { if (!_map.ContainsKey(message)) return null; List
weakActions = _map[message]; actions = new List
(weakActions.Count); for (int i = weakActions.Count - 1; i > -1; --i) { WeakAction weakAction = weakActions[i]; if (weakAction == null) continue; Delegate action = weakAction.CreateAction(); if (action != null) { actions.Add(action); } else { // The target object is dead, so get rid of the weak action. weakActions.Remove(weakAction); } } // Delete the list from the map if it is now empty. if (weakActions.Count == 0) _map.Remove(message); } // Reverse the list to ensure the callbacks are invoked in the order they were registered. actions.Reverse(); return actions; } #endregion // GetActions #region TryGetParameterType ///
/// Get the parameter type of the actions registered for the specified message. /// ///
The message to check for actions. ///
/// When this method returns, contains the type for parameters /// for the registered actions associated with the specified message, if any; otherwise, null. /// This will also be null if the registered actions have no parameters. /// This parameter is passed uninitialized. /// ///
true if any actions were registered for the message
internal bool TryGetParameterType(string message, out Type parameterType) { if (message == null) throw new ArgumentNullException("message"); parameterType = null; List
weakActions; lock (_map) { if (!_map.TryGetValue(message, out weakActions) || weakActions.Count == 0) return false; } parameterType = weakActions[0].ParameterType; return true; } #endregion // TryGetParameterType #region Fields // Stores a hash where the key is the message and the value is the list of callbacks to invoke. readonly Dictionary
> _map = new Dictionary
>(); #endregion // Fields } #endregion // MessageToActionsMap [nested class] #region WeakAction [nested class] ///
/// This class is an implementation detail of the MessageToActionsMap class. /// private class WeakAction { #region Constructor ///
/// Constructs a WeakAction. /// ///
The object on which the target method is invoked, or null if the method is static. ///
The MethodInfo used to create the Action. ///
The type of parameter to be passed to the action. Pass null if there is no parameter. internal WeakAction(object target, MethodInfo method, Type parameterType) { if (target == null) { _targetRef = null; } else { _targetRef = new WeakReference(target); } _method = method; this.ParameterType = parameterType; if (parameterType == null) { _delegateType = typeof(Action); } else { _delegateType = typeof(Action<>).MakeGenericType(parameterType); } } #endregion // Constructor #region CreateAction ///
/// Creates a "throw away" delegate to invoke the method on the target, or null if the target object is dead. /// internal Delegate CreateAction() { // Rehydrate into a real Action object, so that the method can be invoked. if (_targetRef == null) { return Delegate.CreateDelegate(_delegateType, _method); } else { try { object target = _targetRef.Target; if (target != null) return Delegate.CreateDelegate(_delegateType, target, _method); } catch { } } return null; } #endregion // CreateAction #region Fields internal readonly Type ParameterType; readonly Type _delegateType; readonly MethodInfo _method; readonly WeakReference _targetRef; #endregion // Fields } #endregion // WeakAction [nested class] #region Fields readonly MessageToActionsMap _messageToActionsMap = new MessageToActionsMap(); #endregion // Fields }
View Code

  在后面的示例中有简单使用。

五.简单MVVM示例

5.1 View Model定义实现

  实现一个UserViewModel,定义了两个通知属性,3个命令,用于在XAML中实现不同的命令绑定处理,还注册了一个消息,代码:  

public class UserViewModel : BaseNotifyPropertyChanged    {        public string _Name;        public string Name        {            get { return this._Name; }            set { this._Name = value; base.OnPropertyChanged(() => this.Name); }        }        public int _Age;        public int Age        {            get { return this._Age; }            set { this._Age = value; base.OnPropertyChanged("Age"); }        }        public RelayCommand
SetNameCommand { get; private set; } public RelayCommand ShowUserCommand { get; private set; } public RelayCommand
MouseOverCommand { get; private set; } public UserViewModel() { this.SetNameCommand = new RelayCommand
(this.SetName); this.ShowUserCommand = new RelayCommand(this.ShowUser); this.MouseOverCommand = new RelayCommand
(this.MouseOver); Page_MVVM.GlobalMessager.Register("123", () => { MessageBoxX.Info("我是处理123消息的!"); }); } public void SetName(string name) { if (MessageBoxX.Question(string.Format("要把Name值由[{0}]修改为[{1}]吗?", this.Name, name))) { this.Name = name; } } public void ShowUser() { MessageBoxX.Info(this.Name + "---" + this.Age); } public void MouseOver(FrameworkElement tb) { MessageBoxX.Info("我好像摸到了" + tb.Name); } }

5.2 测试页面Page_MVVM.xaml

  创建一个测试页面Page_MVVM,后置代码如下,在构造函数里注入View Model,在一个按钮事件里发送消息:  

public partial class Page_MVVM : Page    {        public static Messenger GlobalMessager = new Messenger();        public Page_MVVM()        {            InitializeComponent();            //set vm            UserViewModel uvm = new UserViewModel();            uvm.Name = "kwong";            uvm.Age = 30;            this.DataContext = uvm;        }        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)        {            GlobalMessager.Notify("123");        }    }

  完整XAML代码:  

ShowUser
Send Message
SetName

5.3 效果

 

 附录:参考引用

 

 

版权所有,文章来源:

个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。

转载地址:http://ippml.baihongyu.com/

你可能感兴趣的文章
编译FFMpeg
查看>>
css-margin
查看>>
深入理解Javascript中的隐式调用
查看>>
kotlin学习笔记——lambda表达式
查看>>
ES6-Promise 源码阅读
查看>>
Java系列3---注解
查看>>
正则表达式
查看>>
小猿圈python学习-字符编码的转换
查看>>
【Android自定义View】绘图之Canvas篇(五)
查看>>
如何提高你技术提问的关注度、回答率?
查看>>
VUE自定义指令
查看>>
DOM 属性总结
查看>>
区块链技术对未来的影响
查看>>
JS设计模式初识(三)-代理模式
查看>>
一次全栈实践心得
查看>>
如今,收入最高编程语言资料汇总!看到就是赚到哦!
查看>>
参数使用小技巧
查看>>
2019年成为优秀的Java开发人员的10个技巧
查看>>
MWeb集成七牛云图床服务、上传图片
查看>>
创业新机:朋友圈广告位大改,“解刨”小程序的真正价值
查看>>