In my WPF app, I want to use an ObservableCollection to contain some Wave classes. Each Wave has an ObservableCollection to contain some Couple class. It looks like:
ObservableCollection<Wave> Waves { get; set; }
- int StartYear { get; set; }
- ObservableCollection<Couple> Couples { get; set; }
- int A { get; set; }
- int B { get; set; }
- some other Properties
After Waves have been added, int properties work well. However, the Couples in every Wave changes when each of them has been changed. They have the same GUID.
How can Coupless in every Wave are different?
Here is my code:
// Couple.cs
public class Couple : DependencyObject
{
public int A { get { return (int)GetValue(AProperty); } set { SetValue(AProperty, value); } }
public int B { get { return (int)GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(int), typeof(Couple), new PropertyMetadata(0));
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(int), typeof(Couple), new PropertyMetadata(0));
}
// Wave.cs
class Wave : DependencyObject
{
public ObservableCollection<Couple> Couples
{
get { return (ObservableCollection<Couple>)GetValue(CouplesProperty); }
set { SetValue(CouplesProperty, value); }
}
public static readonly DependencyProperty CouplesProperty = DependencyProperty.Register("Couples", typeof(ObservableCollection<Couple>), typeof(Wave), new PropertyMetadata(new ObservableCollection<Couple>()));
}
// XAML of the UserControl
<ItemsControl Grid.Column="1" ItemsSource="{Binding Waves}" ScrollViewer.HorizontalScrollBarVisibility="Visible" Margin="0,0,0,6" Focusable="False" ScrollViewer.CanContentScroll="False" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Width="240">
<ItemsControl ItemsSource="{Binding Couples}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="01" TextAlignment="Center"/>
<TextBox Grid.Column="1" Text="{Binding A}"/>
<TextBox Grid.Column="2" Text="{Binding B}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am new to MVVM. Perviously, the values are got by use Items property of ListBox, but now I want to change these code to adapt to MVVM mode. Thank you!
The problem here is that you set a non-null default value for a dependency property that is of a mutable reference type. All instances of the class that owns the property (i.e. all Wave objects) will use the same default collection object.
The constructor of the Wave class should set an initial value like
class Wave
{
public static readonly DependencyProperty CouplesProperty =
DependencyProperty.Register(
nameof(Couples),
typeof(ObservableCollection<Couple>),
typeof(Wave));
public Wave()
{
Couples = new ObservableCollection<Couple>();
}
...
}
Besides that, you should not derive your view model classes from DependencyObject. They should instead implement the INotifyPropertyChanged interface if necessary:
public class Couple : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int a;
public int A
{
get { return a; }
set
{
a = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(A)));
}
}
private int b;
public int B
{
get { return b; }
set
{
b = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(B)));
}
}
}
class Wave
{
public ObservableCollection<Couple> Couples { get; }
= new ObservableCollection<Couple>();
}
Note that the Couples property is now read-only, and the owning class does hence not need to fire a change notification for that property.
For the INotifyPropertyChanged implementation you would typically have a base class that encapsulates the invokation of the PropertyChanged event, like
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
and use it like
public class Couple : ViewModelBase
{
private int a;
public int A
{
get { return a; }
set { SetValue(ref a, value, nameof(A)); }
}
private int b;
public int B
{
get { return b; }
set { SetValue(ref b, value, nameof(B)); }
}
}
Related
INotifyPropertyChanged implemented, DataContext set.
Here is my TreeView:
<TreeView ItemContainerStyle="{StaticResource MaterialDesignTreeViewItemExtended}" x:Name="TestCasesTree" MinWidth="220" ItemsSource="{Binding TestCases.Children, Mode=TwoWay}">
<i:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedTreeViewItem, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<TreeView.Resources>
<con:TestAssessmentToImagePathConverter x:Key="TestAssessmentConverter" />
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=TwoWay}" DataType="{x:Type data:TestCaseNode}">
<StackPanel Margin="0" Orientation="Horizontal">
<Image Source="{Binding TestAssessment, Converter={StaticResource TestAssessmentConverter}}" RenderOptions.BitmapScalingMode="HighQuality" Width="20" Height="20"/>
<TextBlock Margin="8 2 0 0" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Binding:
ItemsSource="{Binding TestCases.Children, Mode=TwoWay}"
BindableBase class:
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected bool CanExecute
{
get
{
return true;
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Property:
public TestCaseNode TestCases
{
get { return mainWindowModel.TestCases; }
set
{
mainWindowModel.TestCases = value;
SetProperty(ref _testCases, value);
}
}
Children:
public ObservableCollection<TestCaseNode> Children { get; set; }
The place where i edit something:
SelectedTreeViewItem.SetTestAssessmentForCluster(testAssessment);
OnPropertyChanged("Children");
OnPropertyChanged("TestCases");
OnPropertyChanged(nameof(TestCases));
OnPropertyChanged(nameof(TestCases.Children));
Nothing changes in View (but when i debug it in Visual Studio, i clearly see that the object changes)
This works, but it crashes another things:
SelectedTreeViewItem.SetTestAssessmentForCluster(testAssessment);
var tc = TestCases;
TestCases = null;
TestCases = tc;
UPD:
TestCaseNode Class:
public class TestCaseNode : TestCase, IEquatable<TestCaseNode>, IComparable<TestCaseNode>
{
public TestCaseNode Parent { get; set; }
public ObservableCollection<TestCaseNode> Children { get; set; }
public void Add(TestCaseNode node) {...}
public void SetTestAssessmentForCluster(TestAssessment testAssessment, ref TestScores _testScores) {...}
public bool Equals(TestCaseNode other) {...}
public override bool Equals(object obj) {...}
public override int GetHashCode() {...}
public int CompareTo(TestCaseNode other) {...}
}
TestCase Class:
public class TestCase
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; }
public string Description { get; set; }
public ObservableCollection<TestStep> TestSteps { get; set; }
public TestAssessment TestAssessment { get; set; } = TestAssessment.EMPTY;
}
The OnPropertyChanged("TestCases"); must be issued by the object that includes the property that changed. In your case, your class TestCaseNode will need to call the OnPropertyChanged method in order for the Treeview to recognize the change to the collection because that object holds the collection. That is, it is not simply some any property named "TestCases" that has changed but the property in a specific object. The reason: you could have more than one properties with this name in different classes. And, if there are multiple copies of the object, they each have a property with the same name.
What I have done, and this has worked for me, is add a public method within the TestCaseNode called UpdateProperties() that then calls OnPropertyChanged("TestCases"); This ensures that the property update is issued by the correct object. Then I call this method when I need to have the control updated.
There are several other ways to do the same thing but I find this a simple and direct approach. You could, for example, include `OnPropertyChanged("TestCases"); in your add method. I have not done this because 1) the OnPropertyChanged call occurs too often and 2) without the OnPropertyChanged call I can defer the update to the control until after I've made several or all of my Add's.
I have a ViewModel with all the properties that i will need in every sub ViewModel.
It's the first time i try to split commands and viewmodel to multiple files. Last time everything was in the same ViewModel and it was a pain to work with it. Everything shows up as expected but i want to find a way to pass the same data in every viewmodel.
From my GetOrdersCommand, i want to get the HeaderViewModel.SelectedSource property. I didn't find any way to do it without getting a null return or loosing the property data...
I would like to call my GetOrdersCommand from HeaderView button too.
Any tips how i can achieve this ? Perhaps, my design is not good for what i'm trying to do ?
MainWindow.xaml
<views:HeaderView Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding HeaderViewModel}" LoadHeaderViewCommand="{Binding LoadHeaderViewCommand}"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding OrderViewModel}" GetOrdersCommand="{Binding GetOrdersCommand}"/>
</TabItem>
</TabControl>
HeaderView.xaml
<DockPanel>
<ComboBox DockPanel.Dock="Left" Width="120" Margin="4" VerticalContentAlignment="Center" ItemsSource="{Binding SourceList}" SelectedItem="{Binding SelectedSource}" DisplayMemberPath="SourceName"/>
<Button x:Name="btnTest" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="4" Content="Test"/>
</DockPanel>
HeaderView.xaml.cs
public partial class OrderView : UserControl
{
public ICommand GetOrdersCommand
{
get { return (ICommand)GetValue(GetOrdersCommandProperty); }
set { SetValue(GetOrdersCommandProperty, value); }
}
public static readonly DependencyProperty GetOrdersCommandProperty =
DependencyProperty.Register("GetOrdersCommand", typeof(ICommand), typeof(OrderView), new PropertyMetadata(null));
public OrderView()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (GetOrdersCommand != null)
{
GetOrdersCommand.Execute(this);
}
}
}
MainViewModel.cs
private OrderViewModel orderViewModel;
public OrderViewModel OrderViewModel { get; set; } // Getter, setter with OnPropertyChanged
private HeaderViewModel headerViewModel;
public HeaderViewModel HeaderViewModel { get; set; } // Getter, setter with OnPropertyChanged
public MainViewModel()
{
HeaderViewModel = new HeaderViewModel();
OrderViewModel = new OrderViewModel();
}
HeaderViewModel.cs
public ICommand LoadHeaderViewCommand { get; set; }
public HeaderViewModel()
{
LoadHeaderViewCommand = new LoadHeaderViewCommand(this);
}
GetOrdersCommand.cs
public class GetOrdersCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly OrderViewModel _orderViewModel;
public GetOrdersCommand(OrderViewModel orderViewModel)
{
_orderViewModel = orderViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
/* Build Order List according to HeaderViewModel.SelectedSource */
_orderViewModel.Orders = new ObservableCollection<Order>()
{
new Order { ID = 1, IsReleased = false, Name = "Test1"},
new Order { ID = 2, IsReleased = true, Name = "Test2"},
};
}
}
Thanks guys ! I moved my commands to their owning ViewModel as suggested.
I tried MVVVM Light Tools and found about Messenger Class.
I used it to send my SelectedSource (Combobox from HeaderView) from HeaderViewModel to OrderViewModel. Am i suppose to use Messenger class like that ? I don't know, but it did the trick!!!
I thought about moving GetOrdersCommand to OrderViewModel, binding my button command to OrderViewModel, binding SelectedSource as CommandParameter but i didn't know how i was suppose to RaiseCanExecuteChanged when HeaderViewModel.SelectedSource changed... Any advice?
MainWindow.xaml
<views:HeaderView DataContext="{Binding Source={StaticResource Locator}, Path=HeaderVM}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding Source={StaticResource Locator}, Path=OrderVM}"/>
</TabItem>
</TabControl>
OrderViewModel.cs
private ObservableCollection<Order> _orders;
public ObservableCollection<Order> Orders
{
get { return _orders; }
set
{
if (_orders != value)
{
_orders = value;
RaisePropertyChanged(nameof(Orders));
}
}
}
public OrderViewModel()
{
Messenger.Default.Register<Source>(this, source => GetOrders(source));
}
private void GetOrders(Source source)
{
if (source.SourceName == "Production")
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 1, IsReleased = false, Name = "Production 1" }
};
}
else
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 2, IsReleased = true, Name = "Test 1" }
};
}
}
Part of HeaderViewModel.cs
private Source _SelectedSource;
public Source SelectedSource
{
get { return _SelectedSource; }
set
{
if (_SelectedSource != value)
{
_SelectedSource = value;
RaisePropertyChanged(nameof(SelectedSource));
GetOrdersCommand.RaiseCanExecuteChanged();
}
}
}
private RelayCommand _GetOrdersCommand;
public RelayCommand GetOrdersCommand
{
get
{
if (_GetOrdersCommand == null)
{
_GetOrdersCommand = new RelayCommand(GetOrders_Execute, GetOrders_CanExecute);
}
return _GetOrdersCommand;
}
}
private void GetOrders_Execute()
{
Messenger.Default.Send(SelectedSource);
}
private bool GetOrders_CanExecute()
{
return SelectedSource != null ? true : false;
}
Here is a class with undefined variable that needs to be passed into the WPF window.
public class SelectedVal<T>
{
public T val {get;set;}
}
Window:
public partial class SOMEDialogue : Window
{
public List<SelectedVal<T>> returnlist { get { return FullList; } }
public List<SelectedVal<T>> FullList = new List<SelectedVal<T>>();
public SOMEDialogue (List<SelectedVal<T>> inputVal)
{
InitializeComponent();
}
}
So here is the question, how can I do this properly to get the T and have a global variable set in my WPF?
Edited (code edited too):
The purpose for the WPF is:
A list of SelectedVal<T> input
Display this input in this WPF
Depend on the T type, user can do something about this input
When finished a return List<SelectedVal<T>> returnlist can be
accessed
This is the basic idea I'm describing. Let me know if you hit any snags. I'm guessing that the search text and the min/max int values are properties of the dialog as a whole. I'm also assuming that there may be a mixture of item types in the collection, which may be an assumption too far. Can you clarify that?
Selected value classes
public interface ISelectedVal
{
Object Val { get; set; }
}
public class SelectedVal<T> : ISelectedVal
{
public T Val { get; set; }
object ISelectedVal.Val
{
get => this.Val;
set => this.Val = (T)value;
}
}
public class StringVal : SelectedVal<String>
{
}
public class IntVal : SelectedVal<int>
{
}
Dialog Viewmodel
public class SomeDialogViewModel : ViewModelBase
{
public SomeDialogViewModel(List<ISelectedVal> values)
{
FullList = values;
}
public List<ISelectedVal> FullList { get; set; }
private String _searchText = default(String);
public String SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged();
}
}
}
private int _minInt = default(int);
public int MinInt
{
get { return _minInt; }
set
{
if (value != _minInt)
{
_minInt = value;
OnPropertyChanged();
}
}
}
private int _maxInt = default(int);
public int MaxInt
{
get { return _maxInt; }
set
{
if (value != _maxInt)
{
_maxInt = value;
OnPropertyChanged();
}
}
}
}
.xaml.cs
public SOMEDialogue (List<ISelectedVal> inputValues)
{
InitializeComponent();
DataContext = new SomeDialogViewModel(inputValues);
}
XAML
<Window.Resources>
<DataTemplate DataType="{x:Type local:StringVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Search text:</Label>
<TextBox Text="{Binding DataContext.SearchText, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:IntVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Min value:</Label>
<TextBox Text="{Binding DataContext.MinIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<Label>Max value:</Label>
<TextBox Text="{Binding DataContext.MaxIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding FullList}"
/>
</Grid>
What I'm trying to achieve doesn't sounds like rocket science. What I'm trying to create is a custom control to which I could pass a list of UIElements items directly from XAML so each element could be different and embed different objects (grid / textbox / panel etc ... ).
Here is the xaml code I would like to use:
<wpf:TileListDoubleItem>
<wpf:TileListDoubleItem.FrontItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="Hello"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.FrontItem>
<wpf:TileListDoubleItem.BackItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="World"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.BackItem>
</wpf:TileListDoubleItem>
And here is my custom control code:
public partial class TileListDoubleItem : UserControl, INotifyPropertyChanged
{
private bool _flipped;
internal bool CanFlip { get { return true; } }
private bool flipped
{
get {
return this._flipped;
}
set {
this._flipped = value;
DisplayItem = this._flipped ? BackItem : FrontItem;
}
}
public ObservableCollection<TileSide> Sides { get; set; }
public ICommand FlipCommand;
public TileListDoubleItem()
{
InitializeComponent();
FlipCommand = new FlipCommand(this);
flipped = false;
}
private UIElement displayItem { get; set; }
public UIElement DisplayItem
{
get { return this.displayItem; }
set {
if (this.displayItem != value)
{
this.displayItem = value;
OnPropertyChanged("DisplayItem");
}
}
}
public void Flip()
{
try
{
flipped = !flipped;
}
catch (Exception ex)
{
throw ex;
}
}
public UIElement FrontItem
{
get { return (UIElement)GetValue(FrontItemProperty); }
set { SetValue(FrontItemProperty, value); }
}
public static readonly DependencyProperty FrontItemProperty =
DependencyProperty.Register("FrontItem", typeof(UIElement), typeof(TileListDoubleItem), new UIPropertyMetadata(null));
public UIElement BackItem
{
get { return (UIElement)GetValue(BackItemProperty); }
set { SetValue(BackItemProperty, value); }
}
public static readonly DependencyProperty BackItemProperty =
DependencyProperty.Register("BackItem", typeof(UIElement), typeof(TileListDoubleItem), new UIPropertyMetadata(null));
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When I run this, both my FrontItem and BackItem are equal to null and are never set to the UIElement (Grid in this example).
I guess what I'm missing must be very obvious to some people.
Thanks in advance for anyone's help here.
Your properies are set as expected. You can confirm this by for example create two TextBlocks that binds to the properties of your control:
<wpf:TileListDoubleItem x:Name="control">
<wpf:TileListDoubleItem.FrontItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="Hello"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.FrontItem>
<wpf:TileListDoubleItem.BackItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="World"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.BackItem>
</wpf:TileListDoubleItem>
<TextBlock Text="{Binding FrontItem.Children[0].Text, ElementName=control}" />
<TextBlock Text="{Binding BackItem.Children[0].Text, ElementName=control}" />
Obviously the properties won't be set by the time the constructor of TileListDoubleItem returns. The XAML parser needs to instantiate the object before it can set any of its properties, just like you do when you create an instance of a class yourself.
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.