I've got a list of checkboxes bound like this.
<ListBox ItemsSource="{Binding AllThings}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
IsChecked="{Binding Active,Mode=TwoWay}"
Checked="ToggleButton_OnChecked"
Unchecked="ToggleButton_OnUnchecked"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The binding works one-way because I can see that the boxes are checked/unchecked based on my settings from the beginning. I was expecting checking a box to update the underlying viewmodel but it doesn't happen. The breakpoint set on the OnPropertyChanged doesn't get hit. I suspect that it's got to do with the fact that I'm changing a property inside the observed property but due to ignorance I'm not sure.
class Presenter : INotifyPropertyChanged
{
private IEnumerable<Something> _allThings;
public IEnumerable<Something> AllThings
{
get { return _allThings; }
set
{
_allThings = value;
OnPropertyChanged("AllThings");
}
}
public Presenter()
{
_allThings = DataAccessor.GetThings();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(
[CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What can I be missing here?
As far I can see I'm doing precisely as this guy suggests. Obviously I'm missing something but it's beyond me what...
edit
As per request from #Clemens, I also implemented the interface in the Soomething class.
public class Something :INotifyPropertyChanged
{
public int Id { get; set; }
public String Name { get; set; }
public bool Active { get; set; }
public override String ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class Something must implement the INotifyPropertyChanged interface. This means that besides writing
public class Something : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
it also has to actually raise the PropertyChanged event when property values change, e.g.:
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
Related
I have below WPF TextBox:
<TextBox Grid.Column="0"
Grid.Row="1"
Text="{Binding myPath, UpdateSourceTrigger=PropertyChanged}"
Margin="5,8,8,5" />
And its property in the view model is:
public string myPath
{
get => myObject.path;
set
{
// Do some comprobations before assigning the new value
if (comprobationsOk(value))
{
Uri myUri = new Uri(value);
myObject.path = myUri.LocalPath;
this.OnPropertyChanged();
}
}
}
And this is my OnPropertyChanged method in my ViewModelBase class which implements INotifyPropertyChanged:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
As you can see, the actual view model value is transformed in the setter.
When the user starts typing in the TextBox ///my/absolute/path the property in the view model gets //my/absolute/path.
So I expect the TextBox to be updated with //my/absolute/path, but it is not and remains as ///my/absolute/path
Actually it works pretty well with a working view model.
View:
<TextBox Text="{Binding Path, UpdateSourceTrigger=PropertyChanged}" />
View model:
public class ViewModel : NotifyPropertyChangedBase
{
private string _name = string.Empty;
public string Path
{
get => _name;
set
{
// To avoid constant System.UriFormatException, we use the try method
var newValue = Uri.TryCreate(value, UriKind.Absolute, out var result) ? result.LocalPath : value;
Update(ref _name, newValue);
}
}
}
NotifyPropertyChangedBase :
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void Update<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{
if (Equals(field, newValue))
return;
field = newValue;
OnPropertyChanged(propertyName);
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a ComboBox in my View:
<ComboBox Name="comboBox1" ItemsSource="{Binding MandantList}" SelectedItem="{Binding CurrentMandant, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Firma}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Here is my Model:
public class MandantListItem : INotifyPropertyChanged
{
public MandantListItem() { }
string _Firma;
bool _IsChecked;
public string Firma
{
get { return _Firma; }
set { _Firma = value; }
}
public bool IsChecked
{
get
{
return _IsChecked;
}
set
{
_IsChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here is my ViewModel:
public class MaViewModel : INotifyPropertyChanged
{
public ObservableCollection<MandantListItem> MandantList { get { return _MandantList; } }
public ObservableCollection<MandantListItem> _MandantList = new ObservableCollection<MandantListItem>();
private MandantListItem _CurrentMandant;
public MandantListItem CurrentMandant
{
get { return _CurrentMandant; }
set
{
if (value != _CurrentMandant)
{
_CurrentMandant = value;
OnPropertyChanged("CurrentMandant");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
How to fill the ComboBox:
public zTiredV2.ViewModel.MaViewModel MAList = new zTiredV2.ViewModel.MaViewModel();
this.comboBox1.ItemsSource = MAList.MandantList;
MAList.MandantList.Add(new zTiredV2.Model.MandantListItem { Firma = "A", Homepage = "a.com", IsChecked = false });
MAList.MandantList.Add(new zTiredV2.Model.MandantListItem { Firma = "B", Homepage = "b.com", IsChecked = false });
But my item doesnt update ... tried also via IsChecked, but no success either ... when i iterate through MAList, IsChecked is always false. And how can i bind a TextBlock to the selected Firma?
Have a hard time with MVVM, but i like it.
You should set the DataContext of the ComboBox to an instance of your view model. Otherwise the bindings won't work:
this.comboBox1.DataContext = MAList;
Also note that the _MandantList backing field for your property shouldn't be public. In fact, you don't need it at all:
public ObservableCollection<MandantListItem> MandantList { get; } = new ObservableCollection<MandantListItem>();
Setting the DataContext should cause the CurrentMandant property to get set when you select an item in the ComboBox. It won't set the IsChecked property though.
In my view model:
public string MyProperty{ get; set; }
public MyViewModel()
{
MyProperty = "hello";
}
I have defined a string property.
Now, from my page, I want to bind to this property:
Text="{Binding MyProperty}"
but this is not working - no text is being show. What am I missing?
Edit:
My view model inherits from:
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Edit 2:
I have modified my view model:
private string _myProperty;
public string MyProperty
{
get => _myProperty;
set => Set(ref _myProperty, value);
}
public MyViewModel()
{
_myProperty = "hello";
}
and the xaml:
Text="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
but it's still not working.
Edit 3: I think the problem is that the Text property is a registered dependency property of a custom control:
public sealed partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
DataContext = this;
}
public string Text
{
get => (string)GetValue(s_textProperty);
set => SetValue(s_textProperty, value);
}
public static readonly DependencyProperty s_textProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyControl), new PropertyMetadata(null));
}
and in the control's xaml I have:
<TextBlock Text="{Binding Text}" />
This:
<MyControl Text="{Binding MyProperty}"/>
is in the page where I use the custom control.
Your class should implement INotifyPropertyChanged and haveproperty accessors like this:
public event PropertyChangedEventHandler PropertyChanged;
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In XAML:
Text="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Remarks:
Mode = TwoWay - property will change both on UI and in code if changed by either one.
UpdateSourceTrigger - Reacts on the PropertyChanged event.
Also, read about DataContext :)
I recomment adding the PropertyChanged.Fody Nuget (https://www.nuget.org/packages/PropertyChanged.Fody/)
Its simple to implement it.
[AddINotifyPropertyChangedInterface]
public class MyViewModel
{
public string MyProperty { get; set; }
public MyViewModel()
{
MyProperty = "hello";
}
}
As #DavidHruška said, edit the binding in XAML too.
Your setter needs to explicitly call the Observable.Set() Method:
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set { this.Set<string>(ref _myProperty, value); }
}
Unfortunately, autos don't implement INPC for you, so you can't use them. Microsoft had this as a feature request, but it appears to be getting turned down.
I recently started learning Xamarin and I stumbled across the following problem. I have a single label in my XAML file which is bound to a ViewModel property. I am using the ICommand interface to bind a tap gesture to a method in my ViewModel which is supposed to update the label's text. However, it is not updating the "Please touch me once!". I am just wondering what I am doing wrong here?
MainPage xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1"
x:Class="App1.MainPage">
<Label Text="{Binding MessageContent, Mode=TwoWay}"
VerticalOptions="Center"
HorizontalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnLabelTouchedCmd}" />
</Label.GestureRecognizers>
</Label>
</ContentPage>
Code-behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
}
}
ViewModel:
class MainPageViewModel : INotifyPropertyChanged
{
private string _messageContent;
public MainPageViewModel()
{
MessageContent = "Please touch me once!";
OnLabelTouchedCmd = new Command(() => { MessageContent = "Hey, stop toutching me!"; });
}
public ICommand OnLabelTouchedCmd { get; private set; }
public string MessageContent
{
get => _messageContent;
set
{
_messageContent = value;
OnPropertyChanged(value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You're calling OnPropertyChanged with a wrong argument as seen here:
protected virtual Void OnPropertyChanged ([System.Runtime.CompilerServices.CallerMemberName] String propertyName)
It expects the name of the property instead of the value you're passing now. Try this instead:
public string MessageContent
{
get => _messageContent;
set
{
_messageContent = value;
OnPropertyChanged("MessageContent");
}
}
Explaination
The current code isn't working because it is passing the value of the property into OnPropertyChanged.
Instead, we need to pass the name of the property as a string into OnPropertyChanged.
Answer
We can take advantage of the CallerMemberName attribute to make the code more concise and to avoid hard-coding strings when calling OnPropertyChanged.
Adding [CallerMemberName] to the parameter of OnPropertyChanged allows you to call OnPropertyChanged() from the setter of the property, and the property name is automatically passed into the argument.
Updated Method
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Updated ViewModel
class MainPageViewModel : INotifyPropertyChanged
{
private string _messageContent;
...
public string MessageContent
{
get => _messageContent;
set
{
_messageContent = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also look at the ViewModelBase located here, have all your ViewModels inherit from it. You can call just OnPropertyChanged, in either of the two ways below. The first of which will just take the name of the calling member, in this case your public property.
OnPropertyChanged();
OnPropertyChanged("MyProperty");
Edit- this is in extension to Brandon's correct answer
I kinda stuck on data-binding here. So, let's say I have a following Class :
Student.cs
public class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string _name;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name");
}
public bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; NotifyPropertyChanged("IsSelected");
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, I have a UserControl to visualize the class, with the following ViewModel :
StudentVisualizerVM.cs
public class StudentVisualizerVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Student _student;
public Student Student
{
get { return _student; }
set { _student = value ; NotifyPropertyChanged("Student"); }
}
public StudentVisualizerVM()
{
Student = new Student();
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then, I want to use the UserControl in my MainWindow.
MainWindow.Xaml
<ItemsControl Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Student}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:StudentVisualizer/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And finally this is my MainWindow ViewModel look like :
MainWindowVM.cs
public class MainWindowVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Student _student;
public Student Student
{
get { return _student; }
set { _student = value ; NotifyPropertyChanged("Student"); }
}
public MainWindowVM()
{
Student = new Student();
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The problem is that the User Control didn't shows up on MainWindow even though I had initialize the Student Property in MainWindow. Why? Isn't MVVM User Control automatically populate its ViewModel? Or maybe it's StudentVisualizerVM that I need to store in MainWindowVM, instead of the Student itself?
Note That (Updated) :
Above code is not the real one, it's just to simplify my project into the main problem.
Assume each XAML's DataContext has been attached correctly from the XAML.CS.
Why I need to do this? Because I need to detect if the window has been clicked outside the StudentVisualizer then IsSelected is set to false. (This property used for certain Command which need the Student to be selected.) - Something like Select-Deselect-esque actio.
ItemsControl is for displaying a collection of items, therefore ItemsControl.ItemsSource has to bind to an IEnumerable property, while you just bind to a single element.
You want something like :
ItemsSource="{Binding StudentCollection}"
public IEnumerable<Student> StudentCollection
{
get { return new List<Student> { Student }; }
}
Now, should you really use an ItemsControl to display just one item.. that's another question and the answer would be : no.