问题
From my understanding, we can use the INofityProperty in a MVVM style application with code similar to the following
object _SelectedPerson;
public object SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (_SelectedPerson != value)
{
_SelectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
}
Now, I've seen Josh Smith's excellent example where he implements extra code to capture what happens if the developer types in a Property name which isn't recognized, such as a typo!
Please tell me if you hate this, but there is a way to get the method name from the stack trace. So, we could instead implement something like
object _SelectedPerson;
public object SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (_SelectedPerson != value)
{
_SelectedPerson = value;
RaisePropertyChanged(Current.Method);
}
}
}
static class Current
{
public static string Method()
{
StackTrace st = new StackTrace();
return (st.GetFrame(1).GetMethod().Name.Split('_')[1]);
}
}
I can only assume this will always work since the the RaisePropertyChanged event always occurs in the Setter (and if I'm wrong, please do correct me).
Now please note, I'm not in a position to really try this, in that at work (where I can work on bigger projects) I'm still on .NET 2.0 and so WPF/MVVM is a long way off in the future but I'm learning in my own time.
So, my question is from those who have used it, is it really better to have an approach which alerts the user of the error compared to removing the option for the error (or do you feel I've miss-understood something); the thing is, Josh Smith is recognized, an expert in this field, and so if he suggests that approach then normally I'd follow blindly but in this case I can't help but quiz it and feel the need to understand more.
回答1:
The Problem with using the StackTrace is, that it is not properly populated in release builds. To overcome this issue there are several approaches to fix it and make raising the PropertyChanged event easier for developers.
See this question: Implementing INotifyPropertyChanged - does a better way exist? and pick a solution that suits you :)
Personally I prefer the following:
// Helper method
public static PropertyChangedEventArgs CreateArguments<TOwner>(Expression<Func<TOwner, object>> Expression) {
// determine the Name of the property using the Expression
}
// Within the view-model implementations:
private static readonly PropertyChangedEventArgs TitleProperty = CreateArguments<MyViewModel>(m => m.Title);
private string title;
public string Title {
get { return this.title; }
set {
if (!string.Equals(this.title, value) {
this.title = value;
this.OnPropertyChanged(TitleProperty);
}
}
}
By using a static member to pre-generate the PropertyChangedEventArgs, the overhead introduced by inspecting the expression tree is limited. This solution is re-factor safe, so you don't have any magic strings.
I also like the .NET 4.5 Approach using the CallerMemberNameAttribute, but It seems that it doesn't work in Portable Class Libraries.
回答2:
You can do the INotifyPropertyChanged through a abstract base-class. This can look like the following:
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Event, fired when the Property has changed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyExpression">() => this.Param</param>
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var propertyName = ExtractPropertyName(propertyExpression);
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Extracts the propertyname out of the Expression given
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyExpression"></param>
/// <returns></returns>
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
var memberExpression = propertyExpression.Body as MemberExpression;
return memberExpression == null ? null : memberExpression.Member.Name;
}
}
In .Net 4.5 you can make a class like:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
There you just have to call
OnPropertyChanged();
回答3:
I have made my own implementation tweak for managing propertychanged notifications.
The first piece is the classic NotifierBase class :
/// <summary>
/// Base class for all notifying objects (model adapters, view models, etc.)
/// </summary>
public abstract class NotifierBase : INotifyPropertyChanged
{
/// <summary>
/// Private reference to UI thread
/// </summary>
private readonly System.Windows.Threading.Dispatcher _uiThread;
/// <summary>
/// Default Constructor
/// </summary>
protected NotifierBase()
{
_uiThread = Application.Current != null ? Application.Current.Dispatcher : System.Windows.Threading.Dispatcher.CurrentDispatcher;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
/// <summary>
/// Explicit raise of a property changed notification
/// </summary>
/// <param name="e"> </param>
protected void Notify(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
//Debug method used to verify that the property we are about to raise is really a member of the current class instance
CheckProperty(e.PropertyName);
//raises the notification
ToUiThread(() => handler(this, e));
}
}
protected void Notify(params PropertyChangedEventArgs[] e)
{
foreach (var pcea in e)
{
Notify(pcea);
}
}
/// <summary>
/// Dispatch an action to the ui thread
/// </summary>
/// <param name="action"> Action to dispatch </param>
protected void ToUiThread(Action action)
{
if (_uiThread.CheckAccess()) //if we are already in the UI thread, invoke action
action();
else
{
//otherwise dispatch in the ui thread
_uiThread.Invoke(action);
}
}
/// <summary>
/// Check if the raised property is a valid property for the current instance type
/// </summary>
/// <param name="propertyName"> Name of the raised property </param>
[DebuggerStepThrough]
private void CheckProperty(string propertyName)
{
Type type = GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (properties.Any(pi => pi.Name == propertyName)) return;
throw new InvalidOperationException(
string.Format("Trying to raise notification on property \"{0}\" which does not exists on type \"{1}\"",
propertyName, type.Name));
}
}
Basically, this class provides : - An easy UI thread dispatching function - A debug check on property that are notified - A multi property notification function
The second part is centralized class which regroup all the notify event args used in my application. This mainly avoid having multiple same hard coded strings all over the application if a property is used several times. But is is also easier for code refactorization an maintainability in general.
public class Npcea
{
public static readonly PropertyChangedEventArgs BarCode = new PropertyChangedEventArgs("BarCode");
public static readonly PropertyChangedEventArgs Cap = new PropertyChangedEventArgs("Cap");
public static readonly PropertyChangedEventArgs Code = new PropertyChangedEventArgs("Code");
public static readonly PropertyChangedEventArgs Status = new PropertyChangedEventArgs("Status");
public static readonly PropertyChangedEventArgs Comments = new PropertyChangedEventArgs("Comments");
}
So basically, in your view model (or adapter or whatever) you simply notify properties like that
public ResourceStatus Status
{
get { return _status; }
set
{
_status = value;
Notify(Npcea.Status,Npcea.Comments);
}
}
Hope this will help
-- bruno
来源:https://stackoverflow.com/questions/15088225/mvvm-inotifypropertychanged-with-automatic-property-name-implementation