I have an application that use the MVVM pattern and I would like to implement validation when the user is fill the information. For example if min is greater than max I want to disable the button in my view.
I have implemented IDataErrorInfo in the model. Here is my model:
public class MyModel : IDataErrorInfo, INotifyPropertyChanged
{
private string stock;
public string Stock
{
get { return stock; }
set { stock = value; }
}
private double min;
public double Min
{
get { return min; }
set { min = value; OnPropertyChange("Min"); }
}
private double max;
public double Max
{
get { return max; }
set { max = value; OnPropertyChange("Max"); }
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
if (columnName == "Min" || columnName == "Max")
{
if (Min > Max)
{
error = "Min can't be greater than Max";
}
}
return error;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
And my ViewModel has a collection of MyModel (Bound to DataGrid) and a boolean property called CanUpdate. This CanUpdate is bound to IsEnabled Property of update button in my view.
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyModel> stocks;
public ObservableCollection<MyModel> Stocks
{
get { return stocks; }
set { stocks = value; }
}
private bool canUpdate = true;
public bool CanUpdate
{
get { return canUpdate; }
set { canUpdate = value; }
}
public MyViewModel()
{
AddStocks();
}
public void AddStocks()
{
Stocks = new ObservableCollection<MyModel>();
Stocks.Add(new MyModel() { Stock ="Stock 1", Min = 10, Max=20 });
Stocks.Add(new MyModel() { Stock = "Stock 2", Min = 5, Max = 15 });
Stocks.Add(new MyModel() { Stock = "Stock 3", Min = 6, Max = 25 });
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
User can edit the Min and Max value from the DataGrid. While editing whenever a error raised in MyModel has to update the CanUpdate property in MyViewModel. Please guide me to achieve this.
In the view model, when adding items to the collection hook to their PropertyChanged event. In a handler analyse the Error property and modify CanUpdate accordingly.
Related
I have a Xamarin project in which I have a Cart Page. I'm trying to update the data whenever I click on an add or remove button but it doesn't. Here's the code
public class CartViewModel : BindableObject
{
....
private ObservableCollection<OrderDisplay> _ordersList;
public ObservableCollection<OrderDisplay> OrdersList
{
get => _ordersList;
set
{
_ordersList = value;
OnPropertyChanged();
}
}
And then I have AddTotal where I try to update it. It is called when pressing the add or remove button
private async void AddTotal(OrderDisplay oCart, int quantity)
{
decimal total = 0;
int index = 0;
if (oCart != null)
{
foreach (OrderDisplay obj in OrdersList)
{
if (obj.Id == oCart.Id)
{
break;
}
index += 1;
}
OrdersList[index].Quantity = quantity;
OrdersList[index].Total = quantity * OrdersList[index].Price;
//OrdersList = null;
//OrdersList = tempOrdersList;
var order = await _apiService.GetOrderById(OrdersList[index].Id, token);
order.Data.Quantity = OrdersList[index].Quantity;
var orderUpdated = await _apiService.UpdateOrder(Convert.ToString(order.Data.Id), token, order.Data);
if (!orderUpdated)
{
await _messageService.ShowMessageAsync("Error", "Ha ocurrido un error.", "Ok", "");
return;
}
}
foreach (OrderDisplay order in OrdersList)
{
total = order.Total + total;
}
LblTotalCart = string.Format("{0:N2}€", total);
}
For context here is the view
I don't know how to do it. Please help.
EDIT
I tried doing it with INotifyPropertyChanged but gives me NullReference. I don't know if this is correct
public class OrderDisplay : INotifyPropertyChanged
{
//public int Id { get; set; }
//public int Quantity { get; set; }
//public decimal Price { get; set; }
//public decimal Total { get; set; }
//public CrProduct Product { get; set; }
private int id;
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged("Id");
}
}
public int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public decimal price;
public decimal Price
{
get { return price; }
set
{
price = value;
OnPropertyChanged("Price");
}
}
public decimal total;
public decimal Total
{
get { return total; }
set
{
total = value;
OnPropertyChanged("Total");
}
}
public CrProduct product;
public CrProduct Product
{
get { return product; }
set
{
product = value;
OnPropertyChanged("Product");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
INotifyPropertyChanged is the mechanism that the UI uses to determine when it needs to update a bound property. If you are changing property P on class X, then X needs to implement INotifyPropertyChanged and raise a PropertyChanged event when P is modified.
an ObservableCollection<T> only raises events when items are removed or added from the collection. It does not raise events when individual properties on the class T are modified.
I'm trying to implement a datepicker and bind it to a model I've created.
I've made a global tournament object in App.xaml.cs:
private Tournament _tournament;
public Tournament tournament {
get { return _tournament; }
set { _tournament = value; OnPropertyChanged("tournament"); }
}
And I've made an OnStartup override to launch my windows:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//setup score
score = new Score();
// Tournament setup
tournament = new Tournament();
tournament.GamesToWin = 1;
tournament.Games = new List<Game>(1);
tournament.Players = new List<Player>(2) { new Player(), new Player() };
//tournament.TimeAndDate = new DateTime(2021, 11, 22);
tournament.Winner = null;
// Initializing the UserInput
UserInput userInput = new UserInput();
UserInputWindowViewModel userinputViewModel = new UserInputWindowViewModel();
userInput.DataContext = userinputViewModel;
// Opening the UserInput Window
bool? res = userInput.ShowDialog();
// If the UserInput Window is closed, open the next Window
if (res == true)
{
// Opening the MainWindow
MainWindow main = new MainWindow();
main.Show();
}
else
{
Shutdown();
}
}
Model.cs (Tournament.cs):
public class Tournament : INotifyPropertyChanged
{
private Player _winner;
private DateTime? _timeAndDate;
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; OnPropertyChanged("Players"); }
}
private List<Game> _games;
public List<Game> Games
{
get { return _games; }
set { _games = value; OnPropertyChanged("Games"); }
}
private int _gamesToWin;
public int GamesToWin
{
get { return _gamesToWin; }
set { _gamesToWin = value; OnPropertyChanged("GamesToWin"); }
}
public Player Winner
{
get { return _winner; }
set { _winner = value; OnPropertyChanged("Winner"); }
}
public DateTime? TimeAndDate
{
get { return _timeAndDate; }
set { _timeAndDate = value; OnPropertyChanged("TimeAndDate"); }
}
#region PropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel.cs (UserInputWindowViewModel.cs):
public class UserInputWindowViewModel
{
// Calling the current app to access the tournament object globally
public App currentApp = Application.Current as App;
#region The players object
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; }
}
#endregion
#region The DateTime object
private DateTime? _dateTime;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; }
}
#endregion
public UserInputWindowViewModel()
{
Tournament tournament = currentApp.tournament;
Players = tournament.Players;
TournamentDateTime = new DateTime(2021, 11, 22);
tournament.TimeAndDate = TournamentDateTime;
//TournamentDateTime = tournament.TimeAndDate;
}
#region ICommand Members
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
}
#endregion
}
Inside my window (UserInput.xaml) I have the following Datepicker:
<DatePicker SelectedDate="{Binding TournamentDateTime, Mode=TwoWay}"/>
I've set the date inside my viewmodel to 22-11-2021 as test, but when I change the date, it's not changing. What am I doing wrong?
EDIT: I also tried the OnPropertyChanged in the ViewModel, which didn't work
You forgot to implement INotifyPropertyChanged under UserInputWindowViewModel.cs.
public class UserInputWindowViewModel : INotifyPropertyChanged
{
}
Also make sure you are triggering PropertyChanged event in property setter, it's important for two-way binding
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
OnPropertyChanged(nameof(TournamentDateTime);
}
}
I'd also would suggest not to access Application directly from a view model, better pass tournament object as a dependency to a view model as soon it would be better separation of concerns
I managed to fix my problem:
I made a small function to update my global model:
public void updateModel(DateTime? s)
{
currentApp.tournament.TimeAndDate = s;
}
I've added this small bit of code to my getter/setter inside my viewmodel (notice I used DateTime.Now to set it to the current date):
private DateTime? _dateTime = DateTime.Now;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; updateModel(value); }
}
This fixed all my problems with the datepicker. And I didn't have to use INotifyPropertychanged inside my viewmodel because my model already had it implemented.
I am right now using a nested ObservableCollection to fill in the rows of a DataGrid with the inner ObservableCollection holding information regarding each cell as follows:
public class MemoryTable : INotifyPropertyChanged
{
string _Address;
public string Address
{
get
{
return _Address;
}
set
{
if (_Address != value)
{
_Address = value;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
bool _NextRowOverflow;
public bool NextRowOverflow
{
get
{
return _NextRowOverflow;
}
set
{
if (_NextRowOverflow != value)
{
_NextRowOverflow = value;
}
}
}
private ObservableCollection<DataAssets> _DataSpace;
public ObservableCollection<DataAssets> DataSpace
{
get
{
return _DataSpace;
}
set
{
_DataSpace = value;
RaisePropertyChanged("DataSpace");
}
}
public MemoryTable()
{
DataSpace = new ObservableCollection<DataAssets>();
}
public class DataAssets : INotifyPropertyChanged
{
string _Addresses;
public string Addresses
{
get
{
return _Addresses;
}
set
{
if (_Addresses != value)
{
_Addresses = value;
RaisePropertyChanged("Addresses");
}
}
}
string _Values;
public string Values
{
get
{
return _Values;
}
set
{
if (_Values != value)
{
_Values = value;
RaisePropertyChanged("Values");
}
}
}
string _ToolTip;
public string ToolTip
{
get
{
return _ToolTip;
}
set
{
if (_ToolTip != value)
{
_ToolTip = value;
RaisePropertyChanged("ToolTip");
}
}
}
Brush _Color;
public Brush Color
{
get
{
return _Color;
}
set
{
if (_Color != value)
{
_Color = value;
RaisePropertyChanged("Color");
}
}
}
string _ConvertedValue;
public string ConvertedValue
{
get
{
return _ConvertedValue;
}
set
{
if (_ConvertedValue != value)
{
_ConvertedValue = value;
RaisePropertyChanged("ConvertedValue");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
}
}
}
And want it to update whenever I change one of the values in the DataAsset class, however whenever I change a value by such a means:
NextRow.DataSpace[i].Color = Brushes.Yellow;
None of the cells update, the workaround i made for doing this is to clear and rewrite the entire ObservableCollection like this:
ObservableCollection<MemoryTable> Temp = new ObservableCollection<MemoryTable>();
foreach (var item in MemoryTable)
{
if (Temp.IndexOf(item) < 0)
{
Temp.Add(item);
}
}
MemoryTableDisplay.Clear();
foreach (var item in Temp)
{
if (MemoryTableDisplay.IndexOf(item) < 0)
{
MemoryTableDisplay.Add(item);
}
}
By this method I am able to force the UI to display the changes, however when moving further to working on a larger set of data, this method takes too long to accomplish, is it possible to have the inner properties to cause an update for the entire ObservableCollection?
Thank you!
Please try this.
NextRow.DataSpace[i].Color = Brushes.Yellow;
After writing down one more statement like this.
NextRow.DataSpace= NextRow.DataSpace;
This approach seems to work half the time for me.
I can see these lines get executed in the debugger:
agencyListBox.DataBindings.Add(new Binding("DataSource", this.Data.Agencies, "AvailableAgencies"));
agencyListBox.DataBindings.Add(new Binding("SelectedItem", this.Data.Agencies, "SelectedAgency", false, DataSourceUpdateMode.OnPropertyChanged));
The agencies class looks like this:
public AgencyType SelectedAgency
{
get
{
return _selected;
}
set
{
_selected = value;
OnPropertyChanged("SelectedAgency");
}
}
public List<AgencyType> AvailableAgencies
{
get
{
return _availableList;
}
set
{
_availableList = value;
OnPropertyChanged("AvailableAgencies");
}
}
So the fields I reference in the binding do exist.
The DisplayMember is set to "Label" which is defined in the AgencyType class:
public event PropertyChangedEventHandler PropertyChanged;
private string _label { get; set; }
public string Label
{
get { return _label; }
set
{
_label = value;
OnPropertyChanged("Label");
}
}
private string _identifier { get; set; }
public string Identifier
{
get { return _identifier; }
set
{
_identifier = value;
OnPropertyChanged("Identifier");
}
}
public AgencyType()
{
Label = string.Empty;
Identifier = string.Empty;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The values are displayed as desired.
But then when I change the selection, data.Agencies.SelectedAgency is null!
Does anyone have any tips?
I have a few properties in a bindingList for a XtratreeList(DevExress) where a child node needs to show a parentnode'e property. I have the following code.
public abstract class ClassBase : INotifyPropertyChanged
{
protected static int initialId = 0;
private int id;
private int parentID;
private string productName;
private string productType;
private string colorProductType;
private void RaisePropertyChanged(string propertyName)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public int ID
{
get { return id; }
set
{
if ( id == value )
return;
id = value;
RaisePropertyChanged("ID");
}
}
public int ParentID
{
get { return parentID; }
set
{
if ( parentID == value )
return;
parentID = value;
RaisePropertyChanged("ParentID");
}
}
public string ProductName
{
get { return productName; }
set
{
if ( productName == value )
return;
productName = value;
RaisePropertyChanged("ProductName");
}
}
public string ProductType
{
get { return productType; }
set
{
if ( productType == value )
return;
productType = value;
RaisePropertyChanged("ProductType");
RaisePropertyChanged("ColorProductType");
}
}
public string ColorProductType
{
get { return colorProductType ; }
set
{
if (colorProductType == value)
return;
colorProductType = value;
RaisePropertyChanged("ColorProductType");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}`
My requirement is to have the ColorProductType property changed when the ProductType property changes, basically ProductType is a parent node property and ColorProductType - child's. So on changing the parent's property the child's need to be changed. I have both these properties bound to 2 text boxes. So changing the parent prop should change both textboxes, but the vice versa is not true. RaisePropertyChanged("ColorProductType"); within the parent is not working, colorproducttype is null, what is the issue here?
RaisePropertyChanged does not actually update the property. It simply signals the PropertyChanged event. Something somewhere must subscribe to it and update the other property accordingly. Something like this:
public abstract class ClassBase : INotifyPropertyChanged
{
private string productType;
private string colorProductType;
public ClassBase()
{
this.PropertyChanged += HandlePropertyChanged;
}
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "ProductType")
{
// update ColorProductType here
}
}
private void RaisePropertyChanged(string propertyName)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string ProductType
{
get { return productType; }
set
{
if ( productType == value )
return;
productType = value;
RaisePropertyChanged("ProductType");
}
}
public string ColorProductType
{
get { return colorProductType ; }
set
{
if (colorProductType == value)
return;
colorProductType = value;
RaisePropertyChanged("ColorProductType");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Naturally, this is complete overkill. You can update ColorProductType when ProductType is updated and let the PropertyChanged event and databinding handle the textbox update:
public string ProductType
{
get { return productType; }
set
{
if ( productType == value )
return;
productType = value;
// update ColorProductType here
RaisePropertyChanged("ProductType");
}
}