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"; }
public ViewModel() { Username = "test"; }
It works.
XMAL:
<Page.DataContext>
<local1:ViewModel/>
</Page.DataContext>
<TextBlock x:FieldModifier="public" Text="{Binding Username}"/>
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 eg 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 eg, 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.
To fix the update issue related to two different instances of Model
, you must initialize all depending classes (the ViewModel
class and the MyClass
class) with the same Model
instance (shared instance).
Since we have to manually instantiate the ViewModel
in order to pass the Model
instance to the constructor, you must remove the ViewModel
instantiation and DataContext
assignment from the XAML of MainWIndow.xaml (and every other XAML where it is used as DataContext
too):
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var sharedModelInstance = new Model();
// Instantiate the ViewModel using the shared Model instance and assign it to the DataContext
var viewModel = new ViewModel(sharedModelInstance);
this.DataContext = viewModel;
// Initialize MyClass using the shared Model
MyClass mc = new MyClass(sharedModelInstance);
mc.UserData();
}
}
ViewModel.cs
Replace the constructor of the ViewModel
example from above with this one:
public ViewModel(Model model)
{
this.Model = model;
this.Model.UsernameChanged += OnModelUsernameChanged;
}
MyClass.cs
Modify the constructor of MyClass to accept a Model instance which MyClass then uses (instead of creating its own instance):
private Model UserModel { get; }
public MyClass(Model model)
{
this.Model = model;
}
Now, that the ViewModel
is assigned to the DataContext
in the constructor of MainWindow
, you probably miss the design time Intellisense
support. To improve the experience, you must define a design time instance of ViewModel
:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance Type=local:ViewModel}">
</Window>
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.