I want to raise PropertyChanged event for a model with DataContract.
Initially I did this
[DataContract]
public partial class User : INotifyPropertyChanged
{
[DataMember(Name="username")]
public string Username
{
get
{
return this.Username;
}
set
{
this.Username = value;
RaisePropertyChanged("Username");
}
}
}
which gave StackOverflow Exception beacause of Infinite Recursion.
So the solution I come up with is
[DataContract]
public partial class User : INotifyPropertyChanged
{
private string _Username { get; set; }
[DataMember(Name="username")]
public string Username
{
get
{
return this._Username;
}
set
{
this._Username = value;
RaisePropertyChanged("Username");
}
}
}
Although this reflects the Username value to the control binding to "Username", this doesn't look the best way to me. Something is wrong. Also my model has approx 30-40 fields. Is this the right approach or can someone please suggest me a better way.
Thanks
I'd be so tempted to use caller-member-name here (if it is in your target framework):
private string _username;
[DataMember(Name="username")]
public string Username
{
get { return _username; }
set { SetField(ref _username, value); }
}
private void SetField<T>(ref T field, T value,
[CallerMemberName] string memberName = null)
{
if(!EqualityComparer<T>.Default.Equals(field,value))
{
field = value;
RaisePropertyChanged(memberName);
}
}
If caller-member-name isn't supported:
[DataMember(Name="username")]
public string Username
{
get { return this._Username; }
set { SetField(ref _Username, value, "Username"); }
}
[DataContract]
public partial class User : INotifyPropertyChanged
{
private string _Username;
[DataMember(Name="username")]
public string Username
{
get
{
return this._Username;
}
set
{
if(this._Username != value)
{
this._Username = value;
RaisePropertyChanged("Username");
}
}
}
}
Related
I have a c# User model which is synced with the Azure Mobile services.
The definition of the User class is as follows:
public class Users : INotifyPropertyChanged
{
[JsonProperty("Id")]
private string id;
[JsonIgnore]
public string Id
{
get { return id; }
set { id = value; OnPropertyChanged("Id"); }
}
[JsonProperty("Name")]
private string name;
[JsonIgnore]
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
[JsonProperty("Email")]
private string email;
[JsonIgnore]
public string Email
{
get { return email; }
set { email = value; OnPropertyChanged("Email"); }
}
[JsonProperty("Password")]
private string password;
[JsonIgnore]
public string Password
{
get { return password; }
set { password = value; OnPropertyChanged("Password"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(propertyName != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Now, in the XAML page code, once the user logs in, I try to get the user, like so:
var user = (await App.MobileService.GetTable<Users>().Where(u => u.Email == this.txtEmail.Text).ToListAsync()).FirstOrDefault();
If I remove the JsonProperty() and JsonIgnore() on private and public members, I get the error: NewtonSoft.Json Deserialization error.
In the above code, is there any simpler way other than declaring JsonProperty() and JsonIgnore on each private and public fields respectively?
Here is my App.xaml.cs
protected override Task OnInitializeAsync(IActivatedEventArgs args) {
Container.RegisterInstance(SessionStateService);
Container.RegisterInstance(NavigationService);
Container.RegisterType<IUserRepository, UserRespository>(new ContainerControlledLifetimeManager());
Container.RegisterType<UserSettingsContext>(new ContainerControlledLifetimeManager());
Container.RegisterType<IUserSettings, UserSettings>(new ContainerControlledLifetimeManager());
var userSettings = this.Container.Resolve<UserSettingsContext>();
userSettings.Database.EnsureCreated();
return base.OnInitializeAsync(args);
}
This is my UserSettings
using using Prism.Windows.Validation;
public class UserSettings : ValidatableBindableBase, IUserSettings {
private string _firstName;
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private UserType _userType;
public UserType UserType {
get { return _userType; }
set { SetProperty(ref _userType, value); }
}
private string _username;
[Required(ErrorMessage = "Required.")]
public string Username {
get { return _username; }
set { SetProperty(ref _username, value); }
}
private string _password;
[Required(ErrorMessage = "Required.")]
public string Password {
get { return _password; }
set { SetProperty(ref _password, value); }
}
}
ViewModel:
public class TestViewModel : UserSettings {
private UserSettings user;
public UserSettings User => user;
public async void SaveSettings() {
//It will work with this line
// var user = new UserSettings() {
// Username= this.Username,
// Password= this.Password,
// UserType= this.UserType,
// FirstName= this.FirstName,
// LastName= this.LastName
// };
//null properties
await loginService.SaveUserSettings(user);
}
}
When I tried to pass the 'user' the properties are null ,even user input data from UI(View), It will work if I setup the UserSettings using the code above, but I want to simplify this without that, how can I do that?
Thanks
You want to write await loginService.SaveUserSettings(this); because this seems to be the target that's filled with data.
I am having a problem with an error...
Error 1 Inconsistent accessibility: parameter type 'HRDMSV1.User' is less accessible than method 'HRDMSV1.FrmAddDoc.FrmAddDoc(HRDMSV1.User)'
All help appreciated?
namespace HRDMSV1
{
public partial class FrmAddDoc : Form
{
User _user;
private ConnStr connStr = new ConnStr();
public FrmAddDoc(User user) /* error here */
{
InitializeComponent();
_user = user;
}
/*...*/
}
class User
{
private String _userName;
private String _password;
private bool _readOnly;
private int _userID;
public String userName {
get { return _userName; }
set { _userName = value; }
}
public String password {
get { return _password; }
set { _password = value; }
}
public bool readOnly {
get { return _readOnly; }
set { _readOnly = value; }
}
public int userID {
get { return _userID; }
set { _userID = value; }
}
}
}
Your User class is less accesable than the public constructor FrmAddDoc which is not allowed. For more reference see CS0051
I have an observable collection of Suppliers that I want to load into a gridview and then have users edit any relevant information on the supplier. My issue is I'm not sure how to implement an IsDirty field for each property on the supplier (Model) that can be changed. I have the IsDirty bits created as such
#region SupplierID
private int _SupplierID;
public int SupplierID
{
get
{
return _SupplierID;
}
set
{
if (_SupplierID != value)
{
_SupplierID = value;
OnPropertyChanged("SupplierID");
}
}
}
#endregion
#region Address
private string _Address;
public string Address
{
get
{
return _Address;
}
set
{
if (_Address != value)
{
_Address = value;
IsDirtyAddress = true;
OnPropertyChanged("Address");
}
}
}
public bool IsDirtyAddress{ get; set; }
#endregion
#region City
private string _City;
public string City
{
get
{
return _City;
}
set
{
if (_City != value)
{
_City = value;
IsDirtyCity = true;
OnPropertyChanged("City");
}
}
}
public bool IsDirtyCity { get; set; }
#endregion
#region State
private string _State;
public string State
{
get
{
return _State;
}
set
{
if (_State != value)
{
_State = value;
IsDirtyState = true;
OnPropertyChanged("State");
}
}
}
public bool IsDirtyState { get; set; }
#endregion
#region Zip
private string _Zip;
public string Zip
{
get
{
return _Zip;
}
set
{
if (_Zip != value)
{
_Zip = value;
IsDirtyZip = true;
OnPropertyChanged("Zip");
}
}
}
public bool IsDirtyZip { get; set; }
#endregion
The problem is that when I build the list of suppliers (ViewModel), I actually end up setting the IsDirty bits to true. What is the best way to set my Address, City, State, Zip when creating the supplier without setting the IsDirty bits to true. Do I need an initialization function in my Model?
for (int i = 0; i < dtSupplier.Rows.Count; i++)
{
Supplier s = new Supplier()
{
SupplierID = Convert.ToInt32(dtSupplier.Rows[i]["SupplierID"].ToString()),
Address = dtSupplier.Rows[i]["Address"].ToString(),
City = dtSupplier.Rows[i]["City"].ToString(),
State = dtSupplier.Rows[i]["State"].ToString(),
Zip = dtSupplier.Rows[i]["Zip"].ToString()
};
Suppliers.Add(s);
}
Maybe I'm going about the whole IsDirty approach the wrong way. I just want to know which values actually changed so my SQL update statement will only include the changed values when a user saves. Thanks!
You need to do a few things:
Add a flag to your ViewModel and name it Loading. When you are loading the ViewModel, set the Loading property to true. When finished loading, set it to false.
Pass your model to your ViewModel but do not expose it. Simply store it in your ViewModel.
When the property is set, check if the ViewModel is in state Loading and do not set IsDirty flags. Also, even if not in loading state, compare the values to the value in your model and see if they are the same.
Do not use hardcoded strings because it is easy to make a mistake. Use nameof (see my example below).
Do not let other people from outside set the IsDirty flag so make the setter private.
I am pretty sure there are libraries that do this already but I do not know of any so perhaps someone else can chime in.
Here is a hypothetical example:
public class Model
{
public string Name { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private readonly Model model;
public ViewModel(Model model)
{
this.model = model;
}
public bool Loading { get; set; }
public bool IsDirtyName { get; private set; }
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (this.Loading)
{
this.name = value;
return;
}
if (this.model.Name != value)
{
IsDirtyName = true;
OnPropertyChanged(nameof(Name));
}
}
}
private void OnPropertyChanged(string propertyName)
{
// ...
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you pay attention the above design, you do not even need all those IsDirty flags and IsLoading etc. You can actually just have one method in your ViewModel that you call during saving and ask it to check and return all the properties that have changed. The ViewModel will compare its own properties against the Model properties and return a dictionary. There are many ways do achieve what you want.
One option is to handle the IsDirty logic on a different class which will store the original values of the Supplier object instance. You can then use that class to GetChangedPropertyNames or check if your object HasChanges.
class Supplier
{
private string _Address;
public string Address
{
get
{
return _Address;
}
set
{
if (_Address != value)
{
_Address = value;
OnPropertyChanged("Address");
}
}
}
}
class SupplierIsDirtyTracker
{
private Dictionary<string, object> _originalPropertyValues = new Dictionary<string, object>();
private Supplier _supplier;
public void Track(Supplier supplier)
{
_supplier = supplier;
_originalPropertyValues.Add(nameof(Supplier.Address), supplier.Address);
}
public bool HasChanges()
{
return !Equals(_originalPropertyValues[nameof(Supplier.Address)], _supplier.Address);
}
public IEnumerable<string> GetChangedPropertyNames()
{
if(!Equals(_originalPropertyValues[nameof(Supplier.Address)], _supplier.Address))
{
yield return nameof(Supplier.Address);
}
}
}
You can also use Reflection on your IsDirtyTracker class to eliminate hardcoding the property names.
I have a very simple C# class:
namespace mybox
{
public class userAccount : IMyInterface
{
public userAccount()
{
}
private int _userId;
private string _userName;
public int userId
{
get { return _userId; }
set { userId = value; }
}
public string userName
{
get { return _userName; }
set { userName = value; }
}
public string list(int myUserId)
{
...
myOutPut = string.Format("{0} {1} {2}", u.userId, u.userName);
return myOutPut.ToString();
}
public void add()
{
pillboxDataContext db = new pillboxDataContext();
userAccount newUser = new userAccount();
newUser.userName = "test123";
db.SubmitChanges();
}
}
}
In my default.aspx.cs in the Page_Load event I'm trying to call the list method:
protected void Page_Load(object sender, EventArgs e)
{
pillbox.userAccount myUA = new pillbox.userAccount();
myUA.add();
// Console.WriteLine(myUA.list(1));
}
When I call the add method I can see that it is trying to assign the value test123 to the property but I get the following message:
An unhandled exception of type 'System.StackOverflowException' occurred in App_Code.1zm0trtk.dll
Any ideas of what I'm doing incorrectly?
The problem is with the way you defined your properties.
You are trying to refer to the property to which you are assigning the value in the setter
which is resulting in an infinte recursion (to be specific this line is triggering it newUser.userName = "test123";).
Change them to:
public int userId
{
get { return _userId; }
set { _userId = value; }
}
public string userName
{
get { return _userName; }
set { _userName = value; }
}
It is because userName calls itself. You probably meant to assign the field:
This line is wrong:
set { userName = value; }
You meant to write:
set { _userName = value; }
You need to set the private backing field, not the property. Otherwise you are just going into infinite recursion as you call set on yourself the entire time.
private int _userId;
private string _userName;
public int userId
{
get { return _userId; }
set { _userId = value; }
}
public string userName
{
get { return _userName; }
set { _userName = value; }
}
In your case, you could just use auto implemented properties (I changed the casing to match the guidelines):
public int UserId { get; set; }
public string UserName { get; set; }
If you re-write your setters a bit, it's easy to see what's happening. The get and set on a property are actually compiled down to methods (PropType get_PropName() and void set_PropName(PropType value)), and any references to the property are compiled to calls to the appropriate method. So this code:
int i = myObj.MyIntProp;
myObj.MyIntProp = 6;
compiles to
int i = myObj.get_MyIntProp();
myObj.set_MyIntProp(6);
So your setter
set
{
username = value;
}
actually compiles to
public void set_username(string value)
{
set_username(value);
}
And now the cause of the stack overflow is obvious.