官术网_书友最值得收藏!

Encapsulating common functionality

Probably the most commonly used interface in any WPF application would be the INotifyPropertyChanged interface, as it is required to correctly implement data binding. By providing an implementation of this interface in our base class, we can avoid having to repeatedly implement it in every single View Model class. It is, therefore, a great candidate for inclusion in our base class. There are a number of different ways to implement it depending on our requirements, so let's take a look at the most basic first:

public virtual event PropertyChangedEventHandler PropertyChanged; 
 
protected virtual void NotifyPropertyChanged(string propertyName) 
{ 
  if (PropertyChanged != null)  
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
} 

In all forms of this implementation, we first need to declare the PropertyChanged event. This is the event that will be used to notify the various binding sources and targets of changes to the data bound values in our application. Note that this is the only requirement of the INotifyPropertyChanged interface. There is no NotifyPropertyChanged method that we have to implement, so you may well come across differently named methods that perform the same functionality.

Of course, without the method, just implementing the event would do nothing. The basic idea of this method is that as usual, we first check for null, and then raise the event, passing the raising class instance as the sender parameter and the name of the property that changed in the PropertyChangedEventArgs. We have already seen that the null conditional operator in C# 6.0 provides us with a shorthand notation for this:

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 

Note that the declared access modifier on this method is protected, to ensure that all View Models that derive from this base class will have access to it, while non-deriving classes will not. Furthermore, the method is also marked as virtual, so that the derived classes can override this functionality if required. In the View Models, this method would be called from a property like this:

private string name = string.Empty; 
 
public string Name 
{ 
  get { return name; } 
  set 
  { 
    if (name != value) 
    { 
      name = value; 
      NotifyPropertyChanged("Name"); 
    } 
  } 
} 

However, a new attribute was added in .NET 4.5, that gives us a shortcut to use with this implementation. The CallerMemberNameAttribute class enables us to automatically obtain the name of the method caller, or more specifically in our case, the name of the property that called the method. We can use it with an optional input parameter with a default value, like this:

protected virtual void NotifyPropertyChanged( 
  [CallerMemberName]string propertyName = "") 
{ 
  PropertyChanged?.Invoke(this, 
    new PropertyChangedEventArgs(propertyName)); 
} 

The calling property can then be simplified to this:

public string Name 
{ 
  get { return name; } 
  set { if (name != value) { name = value; NotifyPropertyChanged(); } } 
} 

It's worth noting at this point that in .NET 4.5.3, another improvement to calling the most basic implementation of this method was introduced. The nameof operator also enables us to avoid using strings to pass the property name, as passing strings can be error prone. This operator basically converts the name of a property, variable, or method to a string at compile time, so the end result is exactly the same as passing the string, but less error prone when renaming definitions. Using the preceding property as an example, let's see how this operator is used:

NotifyPropertyChanged(nameof(Name)); 

There are also other tricks that we can employ too. For example, we often need to notify the Framework that more than one property value has changed at once. Visualize a scenario where we have two properties named Price and Quantity, and a third property named Total. As you can imagine, the value of the Total property will come from the calculation of the Price value multiplied by the Quantity value:

public decimal Total 
{ 
  get { return Price * Quantity; } 
} 

However, this property has no setter, so where should we call the NotifyPropertyChanged method from? The answer is simple. We need to call it from both of the constituent property setters, as they can both affect the resulting value of this property.

Traditionally, we would have to call the NotifyPropertyChanged method once for each constituent property and once for the Total property. However, it is possible to rewrite our implementation of this method to enable us to pass multiple property names to it in a single call. For this, we can make use of the params keyword to enable any number of input parameters:

protected void NotifyPropertyChanged(params string[] propertyNames) 
{ 
  if (PropertyChanged != null) 
  { 
    foreach (string propertyName in propertyNames) 
    { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 
  } 
} 

When using the params keyword, we need to declare an array type input parameter. However, this array merely holds the input parameters and we do not need to supply an array when calling this method. Instead, we provide any number of input parameters of the same type and they will be implicitly added to the array. Going back to our example, this enables us to call the method like this:

private decimal price = 0M; 
 
public decimal Price 
{ 
  get { return price; } 
  set  
  {  
    if (price != value)  
    { 
      price = value;  
      NotifyPropertyChanged(nameof(Price), nameof(Total));  
    } 
  } 
} 

We therefore have a variety of different ways to implement this method, depending on what suits our requirements. We can even add a number of overloads of the method to provide the users of our framework with more choices. We'll see a further enhancement to this method later, but for now, let's see what our BaseViewModel class might look like so far:

using System.ComponentModel; 
using System.Runtime.CompilerServices;
namespace CompanyName.ApplicationName.ViewModels { public class BaseViewModel : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void NotifyPropertyChanged(
params string[] propertyNames)
{ if (PropertyChanged != null) { foreach (string propertyName in propertyNames) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } protected virtual void NotifyPropertyChanged( [CallerMemberName]string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion } }

To summarize, we started with an interface that declared a single event. The interface itself provides no functionality and in fact, we as the implementers, have to provide the functionality, in the form of the NotifyPropertyChanged method and the calling of that method each time a property value changes. But the reward for doing this is that the UI controls are listening and responding to those events and so, by implementing this interface, we have gained this additional data binding capability.

However, we can provide functionality in our application framework in a number of different ways. The two main ways are through the use of base classes and interfaces. The main difference between these two approaches relate to the amount of development that the users of our framework will have to accomplish in order to create the various application components.

When we use interfaces, we are basically supplying a contract that the developers will have to honor, by providing the implementation themselves. However, when we use base classes, we are able to provide that implementation for them. So generally, base classes provide ready-written functionality, whereas interfaces rely on the developers to provide some or all of that functionality for themselves.

We've just seen an example of implementing an interface in our View Model base class. Let's now take a look at what else we can encapsulate in our other framework base classes and compare the differences between providing features or functionality in base classes and interfaces. Let's turn our attention to our Data Model classes now.

主站蜘蛛池模板: 崇礼县| 交城县| 马龙县| 柘城县| 车险| 宁津县| 铜川市| 巴林右旗| 白玉县| 岳普湖县| 邵东县| 始兴县| 乌兰县| 高淳县| 渑池县| 东方市| 师宗县| 华容县| 博爱县| 崇义县| 新干县| 阜南县| 诸暨市| 普安县| 西安市| 新密市| 肇东市| 东城区| 六盘水市| 柳江县| 龙游县| 策勒县| 泸溪县| 洪泽县| SHOW| 得荣县| 马鞍山市| 抚顺市| 通州市| 德保县| 开远市|