How do I bind a Label to a property in Xamarin? - c#

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;
}
}

Related

Implementing ObservableCollection with INotifyChanged

It's a closed network and I don't have a VS on the internet computer, so sorry in advance for how the code looks.
Here is part of my view model:
public class Elements : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String p)
{​
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
private int _CardNumber;
private int _CardCode;
private Card _CurrentlySelectedRow;
public int CardNumber
{
get { return _CardNumber; }
set
{
if (value != _CardNumber)
{
_CardNumber = value;
OnPropertyChanged("CardNumber");
}
}
}
public int CardCode
{
get { return _CardCode; }
set
{
if (value != _CardCode)
{
_CardCode = value;
OnPropertyChanged("CardCode");
}
}
}
public Card CurrentlySelectedRow
{
get { return _CurrentlySelectedRow; }
set
{
if (value != _CurrentlySelectedRow)
{
_CurrentlySelectedRow= value;
OnPropertyChanged("CurrentlySelectedRow");
}
}
}
public class Card
{
public int CardNumber { get; set; }
public int CardCode { get; set; }
public Card() {}
public Card(int CardNumber_, int CardCode_)
{
CardNumber = CardNumber_;
CardCode = CardCode_;
}
}
private ObservableCollection<Card> _Cards { get; set; }
public ObservableCollection<Card> Cards
{
get
{
if (_Cards == null)
_Cards = new ObservableCollection<Card>();
return _Cards;
}
set
{
_Cards = value;
OnPropertyChanged("Cards");
}
}
public bool UpdateCard()
{
//selected row in gridcontrol is binded to CurrentlySelectedRow
CurrentlySelectedRow.CardNumber = CardNumber;
CurrentlySelectedRow.CardCode = CardCode ;
}
public bool AddCard()
{
Cards.Add(new Card(CardNumber, CardCode );
}
}​
Since the grid is not editable, the row is updated in external form, in which controls are binded to cardNumber and cardCode, and when pressing OK - the UpdateCard() is called (if we in update), and the AddCard called if we in add.
The AddCard works - the grid updates.
The UpdateCard - updates the list, but the grid isn't updated...
Maybe you can see were is the problem?...
My intial thought on this is that the update to the CurrentlySelectedRow.CardNumber and CurrentlySelectedRow.CardCode wont call OnPropertyChanged().
With ObservableCollection the UI will be updated if any items are added or removed but will not if any changes are made to the properties in an item.
Because no call is made, the UI doesn't know that these properties have changed.
You would have to make your Card class implement INotifyPropertyChanged similarly to Elements.
What I would normally do is create a BaseObject class that simply implements INotifyPropertyChanged. This class is then inherited by all of my other classes:
public abstract BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string p)
{​
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
Then just change the properties in your Card class to call OnPropertyChanged.
Edit following on from comments:
Now that you've got INotifyPropertyChanged implemented you could try rebinding whatever is bound to Elements.CardNumber and Elements.CardCode to CurrentlySelectedCard.CardNumber and CurrentlySelectedCard.CardCode respectively. This would potentially allow you to delete the properties from Elements.
This will, however depend on the rest of your code.

AutoUpdate Total box using mvvm

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} />

How to make the view update when the view model changes in MVVM?

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.

MVVM Bindings not working properly

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();
}

do not work "set" in some of property in design time

why in desin time do not work set "dataGrid" Property in my code.but property font,with working correct.
I used of this componet on the a Form.(i test it with design time debuging)
enter code here
namespace Example
{
public partial class Component1 : System.Windows.Forms.DataGridView
{
public Component1()
{
}
private DataGridViewColumn _gridcolumn;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TypeConverter(typeof ( ExpandableObjectConverter))]
public DataGridViewColumn gridcolumn
{
get
{
if (_gridcolumn == null)
_gridcolumn = new DataGridViewColumn();
return _gridcolumn;
}
set //Do not Work Set in designTime
{
_gridcolumn = value;
}
}
private System.Drawing.Font _MyFont;
public System.Drawing.Font MyFont
{
get
{
if (_MyFont == null)
_MyFont = new System.Drawing.Font("tahoma", 8);
return _MyFont;
}
set
{
_MyFont = value; //Work correctly in design time
}
}
int _with;
public int withcustom
{
get
{
return _with;
}
set
{
_with = value; //Work correctly in design time
}
}
}
}
Short Version
Either remove DesignerSerializationVisibility(DesignerSerializationVisibility.Content) from your property, or change it to DesignerSerializationVisibility(DesignerSerializationVisibility.Visible).
Long Version
Applying DesignerSerializationVisibility(DesignerSerializationVisibility.Content) to your property means that the WinForms designer will not generate code that sets the value of the property, but rather code that sets the value of the properties on the value of that property.
For example, say I have these two types:
public class MyControl : Control
{
public class MyControlProperties
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
private MyControlProperties props;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public MyControlProperties Properties
{
get
{
if(props == null) props = new MyControlProperties();
return props;
}
}
}
When the designer generates the code for MyControl on a form, it will look something like this:
myControl.Properties.Prop1 = "foo";
myControl.Properties.Prop2 = 10;
So, instead of setting the value of the Properties property (which, in this code, is read-only; though it doesn't have to be, properties like this usually are), it's setting the values of the properties on the value of that property.
This is good example:
public partial class SCon : UserControl
{
public SCon()
{
InitializeComponent();
if (Persoanas == null)
{
Persoanas = new List<Persoana>();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Persoan> Persoanas { get; set; }
}
[Serializable]
public class Persoan
{
public int Id { get; set; }
public String Name { get; set; }
}

Categories