I'm working on an application and spent quite some time finding a solution for the following problem. I've tried several things but I'm not sure what's the best approach to solve it.
I've got a setup of a View, ViewModel and a Model. The ViewModel contains a collection of models of which the user can select a specific model (the collection can be empty which makes the selected model null).
The View consists of a Combobox which allows the user to select a model out of a collection of models. The selected model will then be displayed in the view (with various controls such as treeviews, labels, ...).
This is the Model (I will keep it simple for the sake of explanation):
public Model {
private int id;
private String name;
public Model(_id, _name) {
id = _id;
name = _name;
}
public int ID {
get { return id; }
set { id = value; }
}
public String Name {
get { return name; }
set { name = value; }
}
}
And the ViewModel:
public ViewModel : INotifyPropertyChanged {
private ObservableCollection<Model> models = new ObservableCollection<Model>();
private Model selectedModel;
public ViewModel() { }
public Model SelectedModel {
get { return selectedModel; }
set {
selectedModel = value;
NotifyChanged("SelectedModel");
}
}
public Model ModelCellection {
get { return models; }
set {
models = value;
NotifyChanged("ModelCellection");
}
}
public void addModel(Model newModel) {
models.Add(newModel);
}
//variant A
public int ID {
get { return (selectedModel == null) ? 0 : selectedModel.ID; }
set {
if(selectedModel == null)
return;
selectedModel.ID = value;
NotifyChanged("ID");
}
}
public String Name {
get { return (selectedModel == null) ? 0 : selectedModel.Name; }
set {
if(selectedModel == null)
return;
selectedModel.Name = value;
NotifyChanged("Name");
}
}
//variant B (the attributes of the model will be copied when the selected model changes)
public void changeSelectedModel(Model newSelectedModel) {
Name = newSelectedModel.Name;
Id = newSelectedModel.Id;
}
private int id = 0;
private String name = String.Empty;
public int ID {
get { return id; }
set {
id = value;
NotifyChanged("ID");
}
}
public String Name {
get { return name; }
set {
name = value;
NotifyChanged("Name");
}
}
}
My Question is pretty simple: How do I access the relevant public properties of the Model via the ViewModel?
The solutions I've tried so far are:
Variant A: Just change the selected model and use proxy properties to access the properties of the model (problem: The view doesn't get updated when the selected model changes because the Name and Id properties of the ViewModel don't raise a PropertyChanged event)
Variant B: Copy the relevant properties of the model when the selected model is changed (problem: I have to copy the properties of the model)
After talking to some guys in the wpf chat I switched from variant B to variant A but I don't know how to solve the problem of updating the view when the selected model changes? Or would you rather suggest to use a completely different approach to solve this?
I don't know how to solve the problem of updating the view when the selected model changes?
Just raise PropertyChanged event in your SelectedModel definition
public Model SelectedModel {
get { return selectedModel; }
set {
selectedModel = value;
NotifyChanged("SelectedModel");
NotifyChanged("ID");
NotifyChanged("Name");
}
}
You have to implement INotifyPropertyChanged in your Model.
Your code look like.
Your Model.cs
public Model:INotifyPropertyChanged
{
private int id;
private String name;
public Model(_id, _name)
{
id = _id;
name = _name;
}
public int ID {
get { return id; }
set { id = value; NotifyChanged("ID");}
}
public String Name
{
get { return name; }
set { name = value; NotifyChanged("Name");}
}
}
Your ViewModel.cs
public ViewModel
{
private ObservableCollection<Model> models = new ObservableCollection<Model>();
private Model selectedModel;
public ViewModel() { }
private Model _SelectedModel ;
public Model SelectedModel
{
get { return _SelectedModel??(_SelectedModel=new SelectedModel());}
set { _SelectedModel = value;}
}
}
If property has been Changed it will aromatically Notify because in the ViewModel Model object is present.
You have to set your property like
public void changeSelectedModel(Model newSelectedModel)
{
SelectedModel.Name = newSelectedModel.Name;
SelectedModel.Id = newSelectedModel.Id;
}
and it will Notify.
Related
I want to ask exactly the same question that was asked in this link and also a person answered too but I couldn't understand how to incorporate that in the code. Sorry for asking the noob question, please bear with me.
the link for the question is How to validate child objects by implementing IDataErrorInfo on parent class.
Question is exactly the same and answer is how to achieve but what I find lacking is how to use that class for validation in Employee class.
I'm posting the same question below:
I have two model classes. Parent class has an object of another (child) class as its property. (i mean nested objects and not inherited objects)
public class AddEditItemVM
{
#region Properties/Fields
//validate here: Category cannot be empty, it has to be selected
public Category SelectedCategory
{
get => _SelectedCategory;
set
{
_SelectedCategory = value;
OnPropertyChanged(nameof(SelectedCategory));
}
}
private Category _SelectedCategory = new CCategory();
public Items Source
{
get => _Source;
set
{
_Source = value;
OnPropertyChanged(nameof(Source));
}
}
private Items _Source = new Items();
#endregion Properties/Fields
}
Category class is
public class Category
{
#region Properties/Fields
public long Id
{
get => _Id;
set
{
_Id = value;
OnPropertyChanged(nameof(Id));
}
}
long _Id;
public string Name
{
get => _Name;
set
{
_Name = value;
OnPropertyChanged(nameof(Name));
}
}
private string _Name;
#endregion Properties/Fields
}
Items class is
public class Items
{
#region Properties/Fields
public int CategoryID
{
get => _CategoryID;
set
{
_CategoryID = value;
OnPropertyChanged(nameof(CategoryID));
}
}
int _CategoryID;
//I want to validate this, item name cannot be empty and cannot greater than 20 characters
public string Name
{
get => _Name;
set
{
_Name = value;
OnPropertyChanged(nameof(Name));
}
}
string _Name = String.Empty;
//validate for percentage like percentage should be between 1 and 100
public decimal? TaxPerc
{
get => _TaxPerc;
set
{
//_TaxPerc = value;
_TaxPerc = Properties.Settings.Default.GSTPerc;
OnPropertyChanged(nameof(TaxPerc));
OnPropertyChanged(nameof(PricePlsTax));
OnPropertyChanged(nameof(TaxAmount));
}
}
decimal? _TaxPerc = null;
//price cannot be 0
//* required field!
public decimal? Price
{
get => _Price;
set
{
_Price = value;
OnPropertyChanged(nameof(Price));
OnPropertyChanged(nameof(PricePlsTax));
OnPropertyChanged(nameof(TaxAmount));
}
}
decimal? _Price = null;
#endregion Properties/Fields
}
and the person answered with validation with IDataErrorInfo (which is what I want)
public abstract class DataErrorInfo : IDataErrorInfo
{
string IDataErrorInfo.Error
{
get { return string.Empty; }
}
string IDataErrorInfo.this[string columnName]
{
get
{
var prop = this.GetType().GetProperty(columnName);
return this.GetErrorInfo(prop);
}
}
private string GetErrorInfo(PropertyInfo prop)
{
var validator = this.GetPropertyValidator(prop);
if (validator != null)
{
var results = validator.Validate(this);
if (!results.IsValid)
{
return string.Join(" ",
results.Select(r => r.Message).ToArray());
}
}
return string.Empty;
}
private Validator GetPropertyValidator(PropertyInfo prop)
{
string ruleset = string.Empty;
var source = ValidationSpecificationSource.All;
var builder = new ReflectionMemberValueAccessBuilder();
return PropertyValidationFactory.GetPropertyValidator(
this.GetType(), prop, ruleset, source, builder);
}
}
but I couldn't understand the final part i.e. implementation I mean how to use this class in the implemented class. He said to implement it by making it the parent class which is as below
public AddEditItemVM : DataErrorInfo
{
}
but I wanna know how to bind the validation with the objects.
<TextBox Text="{Binding SelectedCategory.Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
This will bind to your name property of your selectedcategory class and show if there is any validation errors.
I've started working on an application that allows the user to enter some data in different textboxes and then these data are serialized in an XML format, but it seems that I've understood the MVVM concept completely wrong because I've tried to serialize the ViewModel and one of my work colleagues said that is an incorrect way to do things and I should serialize the MODEL.
The "incorrect" implementation:
public class ExampleViewModel : ViewModelBase
{
private double lowerRange;
public double LowerRange
{
get { return lowerRange; }
set
{
lowerRange = value;
RaisePropertyChanged();
}
}
private double upperRange;
public double UpperRange
{
get { return upperRange; }
set
{
upperRange = value;
RaisePropertyChanged();
}
}
}
According to my colleague, serializing this is wrong, but then how should look my MODEL (actually this is the question)
This is correct?
public class ExampleModel
{
public double LowerRange { get; set; }
public double UpperRange { get; set; }
}
public class ExampleViewModel : ViewModelBase
{
private ExampleModel model;
public ExampleViewModel()
{
model = new ExampleModel();
}
private double lowerRange;
public double LowerRange
{
get { return model.LowerRange; }
set
{
model.LowerRange = value;
RaisePropertyChanged();
}
}
private double upperRange;
public double UpperRange
{
get { return model.UpperRange; }
set
{
model.UpperRange = value;
RaisePropertyChanged();
}
}
}
Or this is complety overhead? If you can provide me a meaningful example, I'll be grateful.
Also, I've seen some people that use "RaisePropertyChanged()" inside a MODEL, is this correct? that this model because a ViewModel, right?
Following my comment above, here is how I tend to do this:
public class ExampleModel : ViewModelBase
{
private double _lowerRange;
private double _upperRange;
public double LowerRange
{
get { return _lowerRange; }
set
{
_lowerRange = value;
RaisePropertyChanged();
}
}
public double UpperRange
{
get { return _upperRange; }
set
{
_upperRange= value;
RaisePropertyChanged();
}
}
}
public class ExampleViewModel : ViewModelBase
{
private ExampleModel model;
public ExampleViewModel()
{
Model = new ExampleModel();
}
// this is only needed if you change your complete model
// and need to update the change on the UI
public ExampleModel Model
{
get { return model; }
set
{
model = value;
RaisePropertyChanged();
}
}
}
Also, if you change your ViewModelBase class to something like the one shown here, you can simplify the property getters/setters a bit to the following for example:
public ExampleModel Model
{
get { return model; }
set { SetProperty<ExampleModel >(ref model, value); }
}
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.
It is WPF application and I’m trying to bind individual collection item property in TextBlock. I search on StackOverflow and many others have asked similar questions and they have their solution working. I tried to access value same way but somehow, it’s not displaying Index value in my case so posting similar question. Please help me to identify what I'm doing wrong here.
View model
public class SequeanceViewModel
{
public ObservableCollection<Sequence> SequenceList = new ObservableCollection<ViewModel.Sequence>();
public SequeanceViewModel()
{
for (int i = 1; i <= 6; i++)
{
SequenceList.Add(new ViewModel.Sequence() { Index = i, Name = "Name goes here" });
}
}
}
public class Sequence : INotifyPropertyChanged
{
private int index { get; set; }
private bool current { get; set; }
private string name;
public int Index
{
get
{
return index;
}
set
{
index = value;
OnPropertyChanged(new PropertyChangedEventArgs("Index"));
}
}
public bool Current
{
get
{
return current;
}
set
{
current = value;
OnPropertyChanged(new PropertyChangedEventArgs("Current"));
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Window code
SequeanceViewModel sequeanceViewModel;
public Validation()
{
InitializeComponent();
sequeanceViewModel = new SequeanceViewModel();
this.DataContext = sequeanceViewModel;
}
Binding in xaml
<TextBlock Text="{Binding SequenceList[0].Index, Mode=OneWay}"></TextBlock>
Since you can only bind to public properties, you must define SequenceList as a property and not as a public field:
public ObservableCollection<Sequence> SequenceList { get; } = new ObservableCollection<ViewModel.Sequence>();
You must expose SequenceList as a property instead of a public variable. Otherwise you cannot bind to it.
I have a Common Field Object:
public class Field
{
public string Name { get; set; }
public string oldName { get; set; }
private object _Value = null;
public object Value
{
get
{
return _Value;
}
set
{
_Value = value;
}
}
private FieldType _fieldType = FieldType.Normal;
public FieldType FieldType
{
get
{
return _fieldType;
}
set
{
_fieldType = value;
}
}
private bool _isKey = false;
public bool IsKey
{
get
{
return _isKey;
}
set
{
_isKey = value;
}
}
}
a Common Record Object:
public class Record
{
public string TableName{get;set;}
pubilc FieldCollection _fieldcollection = new FieldCollection();
public FieldCollection FieldCollection
{
get
{
return _fieldcollection;
}
set
{
_fieldcollection = value;
}
}
}
The Data from database to convert to Record Object,and then I want to Binding the Record Data to the Control,but it's not working.
I want to know how can I Binding Data like:
textBox1.DataBindings.Add("Text", listBox1.DataSource , "BarDesc");
I think you want to drag and drop a BindingSource control onto your winform in Design-Time.
Set the BindingSource's DataSource property > Object > Record class. Then set the BindingSource's DataMember.
Select your control (eg Textbox) and set its DataBinding property to the bindingSource control's DataMember.
HTH, at least it should point you in the right direction.