Currently I want the calculations to be dynamic and change on per key stroke. I am trying to do this using MVVM but not entirely sure how.
In the view Model:
public int? Duration { get { return _seb.Duration; } set { _seb.Duration = value;} }
public decimal? Amount { get { return _seb.AmountPer; } set { _seb.AmountPer = value;} }
I have a total Variable And would like it to be constantly updated. May I ask how do I do this.
I tried something like this but no luck
public decimal? Total {get { return _seb.Total; } set { _seb.Total = Amount*Duration; }}
This can be done by raising the ProperyChanged event of the total property when either of the other two property changes.
public class SomeViewModel : ViewModelBase
{
private int? _duration;
private decimal? _amount;
public int? Duration
{
get { return _duration; }
set
{
if (_duration != value)
{
_duration = value;
RaisePropertyChanged("Duration");
RaisePropertyChanged("Total");
}
}
}
public decimal? Amount
{
get { return _amount; }
set
{
if (_amount != value)
{
_amount = value;
RaisePropertyChanged("Amount");
RaisePropertyChanged("Total");
}
}
}
public decimal? Total
{
get
{
if (Amount.HasValue && Duration.HasValue)
return Amount.Value * Duration.Value;
return null;
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You must implement INotifyPropertyChanged and put the formula on get of the total property
public int? Duration
{
get
{
return _seb.Duration;
}
set
{
_seb.Duration = value;
this.RaisePropertyChanged("Total")
}
}
You should show your xaml view as well.
Assuming you are using a textbox, bound to some property in your ViewModel, you can make sure that that property is updated whenever the textbox propery changes by doing something like this:
<TextBox Text={Binding MyVmProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} />
Related
I have a content page "AnimalPage" with labels. I want to bind the labels.Text to the properties of a class so that the labels are automatically updated as the property values change.
The class is "Animal" which has two properties, girth & length. When either value is modified, a 3rd property "weight" is automatically calculated (note: the code that triggers the calcuation is not shown below). When the weight property changes, I want the weight Label on the content page to update automatically.
Many of the examples I've found on Xamarin are XAML which I'm not using.
At the moment, when the page loads, an initial value does show up in the Weight label so it seems like the binding is correct, but when the weight property changes, the label is not updated.
I've put breakpoints in the code and the calcWeight method is being called and the weight property is changing, but weightCell.cellText does not change.
What am I missing?
public class Animal {
public string Name { get; set; }
private double _girth;
// when girth changes, save the value and trigger a re-calculation of weight
public double girth { get { return _girth; } set { _girth = value; this.calcWeight(); } }
private double _length;
// same for length changes; save the value and trigger a re-calculation of weight
public double length { get { return _length; } set { _length = value; this.calcWeight(); } }
private double _weight;
public double weight { get { return _weight; } set { _weight = value; } }
public Animal()
{
...
}
...
public double calcWeight()
{
// formula for weight calculation goes here...
...
this.weight = weight;
return weight;
}
}
The page which displays this class is as follows:
internal class AnimalPage : ContentPage
{
private Animal animal { get; set; }
public AnimalPage(Animal animal)
{
this.animal = animal;
BindingContext = this.animal;
var weightCell = new ResultCell(); // ResultCell is a custom ViewCell
Binding myBinding = new Binding("weight");
myBinding.Source = this.animal;
weightCell.cellText.SetBinding(Label.TextProperty, myBinding);
...
}
}
For the sake of completeness, here is the ResultCell class which is just a custom ViewCell with two labels displayed horizontally.
public class ResultCell : ViewCell {
public Label cellLabel, cellText;
public ResultCell() {
cellLabel = new Label();
cellText = new Label();
var cellWrapper = new StackLayout {
...
Children = { cellLabel, cellText }
};
View = cellWrapper;
}
}
If you want your UI to automatically update when your data changes, your Animal class needs to implement INotifyPropertyChanged, and you need to fire the PropertyChanged event whenever your weight property is modified or recalculated. This event is what alerts the UI that it needs to refresh.
Since this took me so long to figure out I thought I'd post the corrected Animal class in case this helps someone else.
public class Animal : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
private double _girth;
public double girth { get { return _girth; } set { _girth = value; this.calcWeight(); } }
private double _length;
public double length { get { return _length; } set { _length = value; this.calcWeight(); } }
private double _weight;
public double weight { get { return _weight; } set { _weight = value; NotifyPropertyChanged(); } }
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public Animal()
{
...
}
...
public double calcWeight()
{
// formula for weight calculation goes here...
...
this.weight = weight;
return weight;
}
}
I am trying to implement MVVM, but my view is not updating when the view model changes. This is my view model:
public class ViewModelDealDetails : INotifyPropertyChanged
{
private Deal selectedDeal;
public Deal SelectedDeal
{
get { return selectedDeal; }
set
{
selectedDeal = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
In my XAML for the view I have this:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBlock Text="{Binding Path=SelectedDeal.Title, Mode=TwoWay}"></TextBlock>
</StackPanel>
</Grid>
The deal class:
public class Deal
{
private string title;
private float price;
public Deal()
{
this.title = "Example";
}
public Deal(string title, float price)
{
this.title = title;
this.price = price;
}
public string Title
{
get { return title; }
set { title = value; }
}
public float Price
{
get { return price; }
set { price = value; }
}
}
When the application starts the value is correct, but when SelectedDeal changes, the view doesn't. What am I missing?
The path of your binding is nested.To make it work, your Deal class should implement INotifyPropertyChanged too. Otherwise, it will not be triggered unless SelectedDeal is changed. I would suggest you make your view models all inherited from BindableBase. It would make your life much easier.
public class ViewModelDealDetails: BindableBase
{
private Deal selectedDeal;
public Deal SelectedDeal
{
get { return selectedDeal; }
set { SetProperty(ref selectedDeal, value); }
}
}
public class Deal: BindableBase
{
private string title;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
}
The above code should work.
BTW:
If you have no access to the code of Deal class, then to trigger the binding you will have to recreate an instance of SelectedDeal each time when the value of Title is changed.
Update
Managed to fix the selectedIndex problem. I'd forgotten to set SelectedItem as well and naturally that caused a few issues.
So at 9AM this morning we got our 24 hour assignment and I have hit a brick wall.
We're supposed to create a program that allows a supervisor to Add and delete Employees and add Working Sessions, total hours and total earnings. But I am having some problems succesfully implementing this following the MVVM-Pattern. For some reason my Bindings simply aren't working and the only Solution I can see is someone looking over my project and helping me troubleshoot it.
Here is my code - I'm very sorry about having to post the entire thing but given that I have no clue where the problem is I did not see any other options. :
EmployeeModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EmployeeViewModel
public class EmployeeViewModel : ViewModelBase
{
private Employees _employeesModel = new Employees();
public Employees EmployeesView = new Employees();
public ObservableCollection<WorkSessionModel> WorkSessions { get; set; }
private string _id = "0";
private string _name = "noname";
private double _wage = 0;
private int _totalhours = 0;
public string ID
{
get { return _id; }
set { _id = value; RaisePropertyChanged("ID"); }
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public double Wage
{
get { return _wage; }
set
{
_wage = value;
RaisePropertyChanged("Wage");
}
}
public int TotalHours
{
get { return _totalhours; }
set
{
_totalhours = value;
RaisePropertyChanged("TotalHours");
}
}
private EmployeeModel _selectedEmployee = new EmployeeModel();
public EmployeeModel SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
RaisePropertyChanged("SelectedEmployee");
}
}
private int _selectedEmployeeIndex;
public int SelectedEmployeeIndex
{
get { return _selectedEmployeeIndex; }
set
{
_selectedEmployeeIndex = value;
RaisePropertyChanged("SelectedEmployeeIndex");
}
}
#region RelayCommands
// Employee Relay Commands
public RelayCommand EmployeeAddNewCommand { set; get; }
public RelayCommand EmployeeDeleteCommand { set; get; }
public RelayCommand EmployeeNextCommand { set; get; }
public RelayCommand EmployeePrevCommand { set; get; }
public RelayCommand EmployeeTotalHoursCommand { get; set; }
#endregion
public EmployeeViewModel()
{
InitCommands();
}
private void InitCommands()
{
EmployeeAddNewCommand = new RelayCommand(EmployeeAddNewExecute, EmployeeAddNewCanExecute);
EmployeeDeleteCommand = new RelayCommand(EmployeeDeleteNewExecute, EmployeeDeleteCanExecute);
EmployeeNextCommand = new RelayCommand(EmployeeNextExecute, EmployeeNextCanExecute);
EmployeePrevCommand = new RelayCommand(EmployeePrevExecute, EmployeePrevCanExecute);
//EmployeeTotalHoursCommand = new RelayCommand(EmployeeTotalHoursExecute, EmployeeTotalHoursCanExecute);
}
//private void EmployeeTotalHoursExecute()
//{
// _selectedEmployee.TotalHours();
//}
//private bool EmployeeTotalHoursCanExecute()
//{
// return true;
//}
private void EmployeeAddNewExecute()
{
EmployeeModel newEmployee = new EmployeeModel();
EmployeesView.Add(newEmployee);
_employeesModel.Add(newEmployee);
SelectedEmployee = newEmployee;
}
private bool EmployeeAddNewCanExecute()
{
return true;
}
private void EmployeeDeleteNewExecute()
{
if (MessageBox.Show("You are about delete all submissions for Employee," + SelectedEmployee.Name + "(" + SelectedEmployee.ID +")\r\nAre you sure?", "This is a Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_employeesModel.Remove(SelectedEmployee);
EmployeesView.Remove(SelectedEmployee);
}
}
private bool EmployeeDeleteCanExecute()
{
if (SelectedEmployee != null)
return true;
else return false;
}
private void EmployeeNextExecute()
{
SelectedEmployeeIndex++;
}
private bool EmployeeNextCanExecute()
{
if (SelectedEmployeeIndex < EmployeesView.Count - 1)
return true;
return false;
}
private void EmployeePrevExecute()
{
SelectedEmployeeIndex--;
}
private bool EmployeePrevCanExecute()
{
if (SelectedEmployeeIndex > 0)
return true;
return false;
}
}
View
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employees.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
grid_selectedEmployee.DataContext = EmployeeViewModel.SelectedEmployee;
}
}
I can see a few problems with your code:
When the SelectedIndex is updated, SelectedItem remains the same and vice versa.
It looks like your data binding is out of order:
The DataContext property cascades down to every child of a certain dependency object.
The code in the MainWindow constructor should probably be replaced by:
this.DataContext = EmployeeViewModel;
Then in XAML set the rest of the properties using Data Binding. The problem in your situation is that the DataContext of the selectedemployee is only set once. This means if you select another employee, then it will not update.
An example for your SelectedEmployee grid:
<Grid Name="grid_selectedEmployee" DataContext="{Binding SelectedEmployee,
UpdateSourceTrigger=PropertyChanged}">...</Grid>
One of the biggest things I see is you are setting properties, not binding them.
For example,
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
You are telling your DataGrid that it's ItemsSource should be that specific object. You need to bind it to that value so you are telling it to point to that property instead. This will make your UI correctly reflect what's in your ViewModel
The other huge red flag I see is your ViewModel referencing something called and EmployeeView which leads me to believe your View and ViewModel too closely tied together.
Your ViewModel should contain all your business logic and code, while the View is usually XAML and simply reflects the ViewModel in a user-friendly way.
The View and the ViewModel should never directly reference each other (I have had my View reference my ViewModel in some rare occasions, but never the other way around)
For example, an EmployeesViewModel might contain
ObservableCollection<Employee> Employees
Employee SelectedEmployee
ICommand AddEmployeeCommand
ICommand DeleteEmployeeCommand
while your View (XAML) might look like this:
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddEmployeeCommand}" />
<Button Content="Delete" Command="{Binding DeleteEmployeeCommand}" />
</StackPanel>
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
... etc
</DataGrid>
<UniformGrid DataContext="{Binding SelectedEmployee}" Columns="2" Rows="4">
<TextBlock Text="ID" />
<TextBox Text="{Binding Id}" />
... etc
</UniformGrid >
</StackPanel>
And the only thing you should be setting is the DataContext of the entire Window. Usually I overwrite App.OnStartup() to start up my application:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new MainWindow();
var vm = new EmployeesViewModel;
view.DataContext = vm;
view.Show();
}
}
Although I suppose in your case this would also work:
public MainWindow()
{
InitializeComponent();
this.DataContext = new EmployeesViewModel();
}
I've a class like this:
public class PersonViewModel : ViewModelBase //Here is the INotifyPropertyChanged Stuff
{
public PersonViewModel(Person person)
{
PersonEntity = person;
}
public Person PersonEntity {
get { return PersonEntity.Name; }
private set { PersonEntity.Name = value; RaisePropertyChanged("PersonEntity");
}
public string Name {
get { return PersonEntity.Name; }
set { PersonEntity.Name = value; RaisePropertyChanged("Name");
}
public int Age{
get { return PersonEntity.Age; }
set { PersonEntity.Age= value; RaisePropertyChanged("Age");
}
public void ChangePerson(Person newPerson)
{
//Some Validation..
PersonEntity = newPerson;
}
My TextBoxes are bound to Name and Age of the ViewModel.
If I change the _person object in the ViewModel, do I have to call for each Property a RaisePropertyChanged again or is there a way to do this automaticly (in my concret example I have about 15 Properties..)?
Thanks for any help.
Cheers
Joseph
You can indicate all properties have changed by using null or string.Empty for the property name in PropertyChangedEventArgs. This is mentioned in the documentation for PropertyChanged.
One other solution I used to tackle the problem of: first setting the value and then calling the PropertyChangedEventArgs is by adding a Set function in my ViewModelBase which looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
protected bool Set<T>(ref T backingField, T value, [CallerMemberName] string propertyname = null)
{
// Check if the value and backing field are actualy different
if (EqualityComparer<T>.Default.Equals(backingField, value))
{
return false;
}
// Setting the backing field and the RaisePropertyChanged
backingField = value;
RaisePropertyChanged(propertyname);
return true;
}
}
Instead of doing this:
public string Name {
get { return PersonEntity.Name; }
set { PersonEntity.Name = value; RaisePropertyChanged("Name");
}
You can now achieve the same by doing this:
public string Name {
get { return PersonEntity.Name; }
set { Set(ref PersonEntity.Name,value);
}
How do I change the value of TotalPublicationsRead, when the Read property of a Publication has changed?
public class Report
{
public ObservableCollection<Publication> Publications { get; set; }
public int TotalPublicationsRead { get; set; }
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set
{
if (this.read!= value)
{
this.publications = value;
OnPropertyChanged("Read");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Thanks in advance.
If you're trying to do what I think you are, then I'd change the TotalPublicationsRead property and forget about the events. In the code below I just count the items in the list where the Publication has been Read.
The way you were trying to do it you would have to have an event handler for when the ObserableCollection changed. Then you would have to attach an event handler to the PropertyChanged event which would increment or decrement the TotalPublicationsRead property. I'm sure it would work, but it would be a lot more complicated.
public class Report
{
public List<Publication> Publications { get; set; }
public int TotalPublicationsRead
{
get
{
return this.Publications.Count(p => p.Read);
}
}
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set { this.read = value; }
}
}
You can use Dependency Property.
Please check details at:
http://www.wpftutorial.net/dependencyproperties.html
http://msdn.microsoft.com/en-us/library/ms745795(v=vs.110).aspx