If I wanted to create a Attribute (derived from System.Attribute) that hooks into the .NET Build process and translates/converts a standard C# auto property like:
[Notify]
public string Name { get; set; }
to this code, which then is compiled:
private string _nameField;
public string Name
{
get => _nameField;
set
{
if (!EqualityComparer<string>.Default.Equals(_nameField, value))
{
_nameField = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
How would I achive it? What would I have to do?
How can I let the attribute hook into the Build?
As you can see I have no clues at all of the Build process, nor of Roslyn.
But I want to get rid of superfluous MVVM boilerplate code and no longer spent too much time for dull repetitive typing....
Thx, Chris
There's a Fody weaver for this, called PropertyChanged.
Fody is a system for modifying your code at the end of the compile process. The PropertyChanged weaver automatically impements change notification for all properties in all classes that implement INotifyPropertyChanged. It has a number of ways to control the generation using attributes, implementing methods, etc.
The example from their GitHub project page starts like this:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName => $"{GivenNames} {FamilyName}";
}
The weaver interprets the above and generates code like this:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get => givenNames;
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged(InternalEventArgsCache.GivenNames);
OnPropertyChanged(InternalEventArgsCache.FullName);
}
}
}
string familyName;
public string FamilyName
{
get => familyName;
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged(InternalEventArgsCache.FamilyName);
OnPropertyChanged(InternalEventArgsCache.FullName);
}
}
}
public string FullName => $"{GivenNames} {FamilyName}";
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
PropertyChanged?.Invoke(this, eventArgs);
}
}
internal static class InternalEventArgsCache
{
internal static PropertyChangedEventArgs FamilyName = new PropertyChangedEventArgs("FamilyName");
internal static PropertyChangedEventArgs FullName = new PropertyChangedEventArgs("FullName");
internal static PropertyChangedEventArgs GivenNames = new PropertyChangedEventArgs("GivenNames");
}
Of course you won't have access to that version, since it happens somewhere around the end of the compilation pass and your source is unaffected. Debugging the code can be a little difficult.
To help with that the PropertyChanged weaver looks for pre-implemented methods matching various rules and generates code to call those. If you have an OnPropertyChanged method in the class it'll call that instead of generating a boilerplate version. Or you can add an OnFamilyNameChanged method to the class above and that will be called before the OnPropertyChanged method.
You can use Roslyn source generators for that: Source Generators Cookbook
There's an INotifyPropertyChanged example.
Related
I've been using MVVM Light for a while now - it's extremely useful and almost always the first library I add to a new project!
I'm wondering what the implications would be of developing a class that implements INotifyPropertyChanged to encapsulate a bindable property (example below).
public class BindableProperty<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T mValue;
public T Value
{
get { return mValue; }
set
{
if (!EqualityComparer<T>.Default.Equals(mValue, value))
{
mValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
}
public BindableProperty(T default_value)
{
mValue = default_value;
}
}
With this class, I have to change my Xaml, but I believe my ViewModel might be more readable (below) - especially when the number of properties grows.
<TextBox Text="{Binding FirstName.Value, UpdateSourceTrigger=PropertyChanged}"/>
public class MainVM
{
public BindableProperty<string> FirstName { get; private set; }
public BindableProperty<string> LastName { get; private set; }
public MainVM()
{
FirstName = new BindableProperty<string>("");
LastName = new BindableProperty<string>("");
}
}
I know MVVM Light is designed to be extremely flexible, lightweight, and provide complete control (which it does very well). I can of course combine implementations and use the BindableProperty class above for some properties and more explicit ViewModelBase code for other properties in more complex situations.
Am I missing something obvious? What are some of the trade offs for this design that I might not be aware of (e.g. implications of changing xaml binding, data validation...)?
There is no extra value in encapsulating a property. This may work for very simple scenarios, but as soon as your ViewModel becomes more complex, you'll end up wiring your encapsulated classes in odd ways.
This approach won't work very well with validation neither it will if you have properties that depend on each other, i.e. FirstName, LastName and FullName where FullName is just a public string FullName { get { return FirstName+" "+LastName; } }.
Same applies for validation (with IDataErrorInfo). With the base class your code looks like
public string FirstName
{
get { return firstName; }
set
{
if(string.IsNullOrEmpty(value))
{
// where errors is a Dictionary<string, string>
errors.Add(nameof(FirstName), "First name can't be empty.");
return;
}
if(value.Length <2)
{
errors.Add(nameof(FirstName), "First name must be at least 2 characters long.");
return
}
Set(ref firstName, value);
errors.Remove(nameof(FirstName));
}
}
This will be a pain to implement in encapsulated properties
I'm trying to find an elegant solution to implement INotifiyDataErrorInfo with Caliburn.Micro MVVM framework.
I want to limit the amount of code that will be repeated in each VM that needs to implement the validation.
I started by writing a class that inherits Screen and implements INotifiyDataErrorInfo. It works correctly and all is fine, until I need validation on a VM that is not a Screen, but a Conductor.
Of course, I could make a class that inherits Conductor and implements INotifyDataErrorInfo but that's quite annoying as I would have to basically make my own version of all "base" classes of Caliburn.Micro.
One solution I had was to keep the Screen base class and create a IValidator interface that I would inject into my VM, something like this:
public interface IValidator<T> where T : INotifyDataErrorInfo
{
void Validates(T instance);
IEnumerable GetErrors(string propertyName);
bool HasErrors { get; }
void Validate();
void ValidateProperty<TValue>(TValue value, string propertyName = null);
void ValidateProperty<TValue, TProperty>(TValue value, Expression<Func<TProperty>> property);
}
It will then used in the VM in this way.
public class CreateCarViewModel : Conductor<CreateCarViewModel>.Collection.OneActive, INotifyDataErrorInfo
{
private readonly IValidator<CreateCarViewModel> validator;
public CreateExperimentViewModel(IValidator<CreateCarViewModel> validator)
{
this.DisplayName = "Select a car";
this.validator = validator;
this.validator.Validates(this);
}
[Required]
public string CarName
{
get
{
return this.carName;
}
set
{
if (this.carName != value)
{
this.carName = value;
this.validator.ValidateProperty(value, () => this.CarName);
this.NotifyOfPropertyChange(() => CarName);
}
}
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
return this.validator.GetErrors(propertyName);
}
public bool HasErrors
{
get { return this.validator.HasErrors; }
}
...
}
This works pretty nicely, as it is very simple to implement the INotifyDataErrorInfo in the VMs, but the issue I have is triggering the ErrorChanged event. It must be triggered by the implementation of the IValidator as he is the one who knows when the errors have changed, and of course he cannot trigger directly.
One idea I have was to have an event in the IValidator and subscribe to it in the VM so that it can trigger its own event, but I find that it makes a lot of code for nothing.
Does anyone have a better idea?
Thanks
I wrote a small plugin for CM to enable fluent builder-style validation. Maybe it will help you. Feel free to use it: https://github.com/AIexandr/Caliburn.Micro.Validation
Example of usage:
public class PaymentEditorViewModel() : ValidatingScreen
{
public PaymentEditorViewModel()
{
AddValidationRule(() => PaymentSum).Condition(() => PaymentSum <= 0).Message("Please enter payment sum");
}
#region PaymentSum property
decimal _PaymentSum;
public decimal PaymentSum
{
get
{
return _PaymentSum;
}
set
{
_PaymentSum = value;
NotifyOfPropertyChange(() => PaymentSum);
}
}
#endregion
}
The wireup code is not excessive is it, if you have the IValidator expose the same event as the VM, eg:
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void Validate()
{
if (ErrorsChanged != null)
ErrorsChanged(instance, new DataErrorsChangedEventArgs("someProperty"));
}
and in the VM:
validator.ErrorsChanged += (sender, args) => ErrorsChanged(sender, args);
But I guess you already answered your own question without telling us ;)
I have been successfully sending objects using a WCF service, but there is one class that doesn't work. The WCF service will build, and I can invoke the function that returns the object in the WCF Test Client, but I cannot use the service once I have updated the service reference in the project that's the consumer.
Here is a copy of the class
//[Serializable]
[DataContract]
public class TillCompanySystem : INotifyPropertyChanged
{
// Internal private member variables
private String _sTillCompanySystemID = "";
private String _sValue = "";
// Events
//[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
// Constructor
public TillCompanySystem()
{
}
// Public properties
#region Public Properties
// Public properties
[DataMember]
public String TillCompanySystemID
{
get { return _sTillCompanySystemID; }
set
{
_sTillCompanySystemID = value;
OnPropertyChanged(new PropertyChangedEventArgs("TillCompanySystemID"));
}
}
[DataMember]
public String Value
{
get { return _sValue; }
set
{
_sValue = value;
OnPropertyChanged(new PropertyChangedEventArgs("Value"));
}
}
// End region public properties
#endregion
// All objects need to support ToString
public override String ToString()
{
return TillCompanySystemID.ToString();
}
// Public Events
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
I really have no idea why this doesn't work. The only difference I can think of between this and other classes is that previously it was serializable, but I can't see how that would make any difference now. However, if I view the class from the metadata the class still appears to be serializable. I have tried rebuilding everything and that hasn't fixed it.
Does anyone have any ideas?
Thanks in advance
Ed
The reason that it wasn't working was that the someone had made the project reference a compiled version of the datalayer project which meant that this class was always accessible and I have been able to add new classes to the project and it found them, but it wouldn't rebuild this particular class to make it a datacontract since it existed in compiled form.
There seems to be conflicting thoughts on whether INotifyPropertyChanged should be implemented in the Model or not. I think that it should be implemented in the ViewModel, but I can't figure out how it would be accomplished. There are plenty of mentions of this same idea all over stackoverlow.com ( In MVVM model should the model implement INotifyPropertyChanged interface?, In MVVM should the ViewModel or Model implement INotifyPropertyChanged?), but I can't find any example to show how to do it.
Let's say for example I have a model Person:
Public Person {
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public void NameChange( string newName );
}
How would I implement the ViewModel so that changes in Age, FirstName, or LastName are all recognized?
Public PersonViewModel : INotifyPropertyChanged {
Person _person;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName) {
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//ctor, Properties, etc...
}
EDIT - Clarification:
So without changing the Person model how do I modify the ViewModel to get notified of the updates?
Is that even possible? If not, how are those that subscribe to the "INPC in the model is baaaad" get notified of changes in the model?
ViewModel should definitely implement INotifyPropertyChanged. I don't have a strong opinion on whether it should be implemented in the Model as well. I don't think you need it when the model properties don't change independently from the ViewModel while it is bound to the View.
Anyway, this is how I'd implement INotifyPropertyChanged in the ViewModel when it is not already implemented in the Model:
public class PersonViewModel : INotifyPropertyChanged
{
private Person person;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public PersonViewModel(Person person)
{
this.person = person;
}
public int Age
{
get { return person.Age; }
set
{
if (value != person.Age)
{
person.Age = value;
OnPropertyChanged("Age");
}
}
}
public string FirstName
{
get { return person.FirstName; }
set
{
if (value != person.FirstName)
{
person.FirstName = value;
OnPropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return person.LastName; }
set
{
if (value != person.LastName)
{
person.LastName = value;
OnPropertyChanged("LastName");
}
}
}
}
Seeing how you updated you question, I need to add that without having INotifyPropertyChanged (or a similar custom notification event) implemented in the model, you can't get notified about the changes in the model that happen in it independently from the ViewModel. I guess you should be able to avoid that. Otherwise just implement INotifyPropertyChanged in it. There's nothing wrong with that if you need it.
Interesting question. I've read about MVVM for more than a year now, and I'm still not sure about it.
If your application is representing a state of a process for example, and this state is modified internally without any interaction of the user, then your model needs to be able to notify your viewmodel that it changed.
So if your model implement INotifyPropertyChanged, and your viewmodel only pass the same informations to the view, then... does your viewmodel really need to exist...?
In our company, we consider two main cases:
We structure our software with a quite strict UML analysis before developping (not so agile). When we then want to display our objects on screen, they return us their different views, which are used when needed with Bindings (using ContentControl or so). Most of the views we need for our software display these kinds of object, that implement INotifyPropertyChanged and are therefore also kind of ViewModels.
To build the software main Views (view structure), we create global views and ViewModels for them. That's when we really follow the MVVM practices.
Maybe I missed a point about MVVM, but in my experience, it's not a pattern that you absolutely have to always follow. It's a very good way of thinking to develop WPF applications, but creating ViewModels for each and every view seems to me like a big overhead.
What do all of you think of this way of doing?
Best regards,
Antoine
EDIT 31.03.2012
I have found a very interesting article explaining how to handle your model properties in the viewmodel, without having to implement a proxy property in the viewModel for each one of them.
Also the writer say some words about having INPC implemented in the model, and the viewmodel listening to it.
I think this is the most practical oriented article I've read about MVVM so far.
Check it out :
http://msdn.microsoft.com/en-us/magazine/ff798279.aspx
In my experience, Model objects don't have to (and probably shouldn't) know that they are being constructed in a View. Often, Model objects are entities that should never be allowed to be in an invalid state. ViewModel objects are the things that construct the Model objects.
So, since you never want to create a person who is very old or very young, and every person needs a name, your Person class might look like this:
public class Person {
public int Age { get; private set; }
public string Name { get; private set; }
public Person(int age, string name) {
if (age < 0 || age > 150) throw new ArgumentOutOfRangeException();
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException();
Age = age;
Name = name;
}
}
And your PersonViewModel might look like this::
class PersonViewModel : INotifyPropertyChanged {
private int _Age;
private int _Name;
public int Age {
get { return _Age; }
set {
if (_Age.Equals(value)) return;
_Age = value;
RaisePropertyChanged("Age");
}
}
public string Name {
get { return _Name; }
set {
if (_Name.Equals(value)) return;
_Name = value;
RaisePropertyChanged("Name");
}
}
public Person CreatePerson() {
return new Person(_Age, _Name);
}
}
You can then put whatever values you want in your PersonViewModel without worrying about creating an invalid Person object. You can also implement validation in the PersonViewModel that may be more strict than the validation in the Person class (for example, restricting the age range to adults over 18 years old (see IDataErrorInfo)).
Save for the typos you pretty much have it ;)
All you would need to add is your constructor and property definitions:
public class PersonViewModel : INotifyPropertyChanged
{
Person _person;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public PersonViewModel(Person person)
{
_person = person;
}
public int Age
{
get
{
return _person.Age;
}
set
{
_person.Age = value;
OnPropertyChanged("Age");
}
}
}
If you have a choice, I would definitely recommend implementing INotifyPropertyChanged in the Model because you won't havae to worry about translating Models to ViewModels and back.
But if you can't, see above :)
C# - .net 3.5
I have a family of classes that inherit from the same base class.
I want a method in the base class to be invoked any time a property in a derrived class is accessed (get or set). However, I don't want to write code in each and every property to call the base class... instead, I am hoping there is a declarative way to "sink" this activity into the base class.
Adding some spice to the requirement, I do need to determine the name of the property that was accessed, the property value and its type.
I imagine the solution would be a clever combination of a delegate, generics, and reflection. I can envision creating some type of array of delegate assignments at runtime, but iterating over the MemberInfo in the constructor would impact performance more than I'd like. Again, I'm hoping there is a more direct "declarative" way to do this.
Any ideas are most appreciated!
You can't do it automatically, but you can pretty much get 95% for free. This is a classic case for aspect-oriented programming. Check out PostSharp, which has the OnFieldAccessAspect class. Here's how you might solve your problem:
[Serializable]
public class FieldLogger : OnFieldAccessAspect {
public override void OnGetValue(FieldAccessEventArgs eventArgs) {
Console.WriteLine(eventArgs.InstanceTag);
Console.WriteLine("got value!");
base.OnGetValue(eventArgs);
}
public override void OnSetValue(FieldAccessEventArgs eventArgs) {
int i = (int?)eventArgs.InstanceTag ?? 0;
eventArgs.InstanceTag = i + 1;
Console.WriteLine("value set!");
base.OnSetValue(eventArgs);
}
public override InstanceTagRequest GetInstanceTagRequest() {
return new InstanceTagRequest("logger", new Guid("4f8a4963-82bf-4d32-8775-42cc3cd119bd"), false);
}
}
Now, anything that inherits from FieldLogger will get the same behavior. Presto!
I don't believe this is possible to do declaratively, i have never seen it done that way. What you can do though is implement the INotifyPropertyChanged interface on your base class, and have the implementation of the interface in the base class. Something like this:
public class A : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void RaiseOnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName);
}
public A()
{
this.PropertyChanged += new PropertyChangedEventHandler(A_PropertyChanged);
}
void A_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//centralised code here that deals with the changed property
}
}
public class B : A
{
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaiseOnPropertyChanged(this, "MyProperty");
}
}
public string _myProperty = null;
}