I have created an interface (ViewModel) for my application to control my elements. I want to set a text for a Textblock in a class.
ViewModel:
public class Model
{
public string Username { get; set; }
}
internal class ViewModel : INotifyPropertyChanged
{
private Model _model;
public ViewModel()
{
_model = new Model();
}
string username;
public string Username {
get => username;
set
{
if(username != value)
{
username = value;
this.RaisePropertyChanged(nameof(_model.Username));
}
}
}
private void RaisePropertyChanged([CallerMemberName] string name = "")
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(name)));
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MyClass:
Model Model = new Model();
Model.Username = "name";
when i set the Username in public ViewModel() { Username = "test"; }
It works.
XMAL:
<Page.DataContext>
<local1:ViewModel/>
</Page.DataContext>
<TextBlock x:FieldModifier="public" Text="{Binding Username}"/>
CodePudding user response:
You must raise the PropertyChanged
for the property that is the member of binding source. When you bind to a class ViewModel
, you must raise the event for the property on this object and not on any unrelated objects like your class Model
.
Since you don't want to bind directly to classes of the model, you must delegate the properties or write the values back to the model at the proper point in time e.g. after invocation of a save command.
If the model can change data independently and your view model needs to know about these changes, then the view model must observe the model. To enable observation, the model must expose corresponding events.
To delegate the property data received from the view to the model, implement property delegation:
// Update the model immediately after e.g., the data binding has changed the property
public string Username
{
get => this.Model.Username;
set
{
if (this.Model.Username != value)
{
this.Model.Username = value;
RaisePropertyChanged();
}
}
}
To observe data changes in a class of the model, introduce a corresponding event. This event can notify about particular changes e.g., a particular property like UsernameChanged
, or it can be more general and notify about certain state changes like UserDataChaged
or DataChanged
:
ValueChangedEventArgs.cs
public class ValueChangedEventArgs<TValue> : EventArgs
{
public ValueChangedEventArgs(TValue oldValue, TValue newValue)
{
this.OldValue = oldValue;
this.NewValue = newValue;
}
public TValue NewValue { get; }
public TValue OldValue { get; }
}
Model.cs
public class Model
{
protected virtual void OnUsernameChanged(string oldValue, string newValue)
=> this.UsernameChanged?.Invoke(this, new ValueChangedEventArgs<string>(oldValue, newValue));
public event EventHandler<ValueChangedEventArgs<string>> UsernameChanged;
private string username;
public string Username
{
get => this.username;
set
{
if (this.Username != value)
{
string oldValue = this.Username;
this.username = value;
OnUsernameChanged(oldValue, this.Username);
}
}
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Model = new Model();
this.Model.UsernameChanged = OnModelUsernameChanged;
}
private void OnModelUsernameChanged(object sender, ValueChangedEventArgs<string> e)
{
if (e.NewValue != this.Username)
{
RaisePropertyChanged(nameof(this.Username));
}
}
// Update the model immediately after e.g., the data binding has changed the property
public string Username
{
get => this.Model.Username;
set
{
if (this.Model.Username != value)
{
this.Model.Username = value;
RaisePropertyChanged();
}
}
}
private Model Model { get; }
}
Consider to hide details of the model class to add more robustness in terms of modifications to the model's implementation. For example, don't expose the properties of a model class directly. Rather build its API around a set of methods that allow to modify it. You could introduce a SaveUser(User) : void
method, where User
is the class to encapsulate user related data (to reduce the parameter count). Now, instead of having the view model class to know which properties and methods it has to use in order to update a user, it simply invokes the SaveUser()
method. The view model must not know about the internals of the model.
CodePudding user response:
your class should look like this:
(you want to use different name for ModelField probably)
public class Model : INotifyPropertyChanged
{
string username;
public string Username {
get => username;
set
{
if(username != value)
{
username = value;
this.RaisePropertyChanged(nameof(Username));
}
}
... }
internal class ViewModel : INotifyPropertyChanged
{
private Model _model;
public ViewModel()
{
_model = new Model();
}
public Model ModelField {
get => _model;
set
{
if(_model != value)
{
_model = value;
this.RaisePropertyChanged(nameof(ModelField));
}
}
}
...
}
and the xaml should bind to ModelField.UserName