WPF TextBox content in Model layer using MVVM - wpf

How to store the content of TextBox in Model layer to be in line with MVVM?
I've made simple demo application to practice MVVM. It consists of main TextBox and 2 additional TextBoxes just for test if the app works properly.
In ViewModel I have TextContent class which implements INotifyPropertyChanged and it has Text property and the Text of MainTextBox is bindded to this and it works correctly.
In Model I have TextStore property which I try to update in the setter of Text property from ViewModel.TextContent, using simple method ModelUpdate().
And this model updating doesn't work.
Could you tell me ho can I transfer the content of TextBox which is stored in ViewModel property to the Model layer? And being in line in MVVM pattern?
Here the code:
View: (Here, the third TextBox is bindded to the model - I know, this is not compatible with MVVM idea but this is just for check the value of TextStore property from Model layer)
<Window x:Class="MVVM_TBDB_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_TBDB_2"
xmlns:vm="clr-namespace:MVVM_TBDB_2.ViewModel"
xmlns:m="clr-namespace:MVVM_TBDB_2.Model"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<m:TextContent x:Key="ModelTextContent" />
</Window.Resources>
<Window.DataContext>
<vm:TextContent />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="MainTB" Grid.Row="0" Margin="10" AcceptsReturn="True"
Text="{Binding Text, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<Button Name="SaveButton" Content="Save" Grid.Row="1" Margin="10,2" Padding="20,0" HorizontalAlignment="Left" />
<TextBox Name="ControlTB" Grid.Row="1" Margin="30,2,2,2" Width="100" Text="{Binding Text, Mode=OneWay}" />
<TextBox Name="ControlTB2" Grid.Row="1" Margin="300,2,2,2" Width="100" DataContext="{StaticResource ModelTextContent}"
Text="{Binding TextStock, Mode=TwoWay}" />
</Grid>
</Window>
ViewModel:
class TextContent : INotifyPropertyChanged
{
private Model.TextContent model;
public TextContent()
{
model = new Model.TextContent();
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
ModelUpdate(_Text);
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model:
class TextContent
{
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; }
}
}

See Here I have implemented your requirement.
Attach the data context from code behind.
Implement the INotifyPropertyChanged interface in Model.
Make the TextStock property as binded property.
MainWindow.cs
public TextContent _model { get; set; }
public TextContentViewModel _viewModel { get; set; }
public MainWindow()
{
InitializeComponent();
_viewModel = new TextContentViewModel();
_model = new TextContent();
this.DataContext = _viewModel;
ControlTB2.DataContext = _model;
}
Your ViewModel Class
private TextContent model;
public TextContentViewModel()
{
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
if (model != null)
{
ModelUpdate(_Text);
}
else
{
model = ((Application.Current.MainWindow as MainWindow).ControlTB2).DataContext as TextContent;
}
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model Class
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; OnPropertyChanged("TextStock"); }
}
private void OnPropertyChanged(string parameter)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
Note: I have renamed the class names as per my convenience.

Related

Can't manage with TextBox.Text binding with Template assigned

I have model Partner:
public class Partner
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Comment { get; set; }
public override string ToString()
{
return Title;
}
}
view with this xaml:
<Window x:Class="WpfExtandedTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ModelViews="clr-namespace:WpfExtandedTextBox"
d:DataContext="{d:DesignInstance ModelViews:ViewModel}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Window.Resources>
<ControlTemplate x:Key="entityTextBoxTemplate" TargetType="TextBox">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" Text="{Binding Partner.Title, Mode=TwoWay}"/>
<TextBlock MaxWidth="0" Visibility="Hidden" Text="{Binding Partner.Id, Mode=TwoWay}"/>
<Button x:Name="OpenPartnerList" Content="..." Click="OpenPartnerList_Click"/>
<Button x:Name="OpenPartner" Content="O" Click="OpenPartner_Click"/>
<Button x:Name="ClearPartner" Content="X" Click="ClearPartner_Click"/>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Orientation="Vertical" >
<TextBox x:Name="TextBoxSelectedPartner" Template="{StaticResource entityTextBoxTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<!--<Button x:Name="ChoosePartner" Click="ChoosePartner_Click" Content="Choose partner"/>
<DataGrid ItemsSource="{Binding Partners}" AutoGenerateColumns="True" />-->
</StackPanel>
</Window>
and view model:
public class ViewModel : BaseViewModel
{
private List<Partner> partners;
public List<Partner> Partners
{
get { return this.partners; }
}
private Partner partner;
public Partner Partner
{
get { return this.partner; }
set
{
this.partner = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
AppDbContext db = new AppDbContext();
this.partners = db.Partners.ToList();
db.Dispose();
}
}
I'd like to create TextBox with 3 buttons:
1 - for choosing Partner from some list
2 - for opening window with Partner's details
3 - for clearing TextBox
For this purpose I've created ControlTemplate "entityTextBoxTemplate": TextBox is for storing Partner.Title and hidden TextBlock is for storing Partner.Id. I assume that after choosing a Partner from list TextBox and TextBlock will be filled with Title and Id respectively, but it doesn't work and I don't know why. Can anybody help me to solve this issue?
Updated:
Partner is populated in this code:
private void PartnerListView_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
viewModel.Partner = ((PartnerListView)sender).SelectedPartner;
}
Updated 2:
My BaseViewModel:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My fixed BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I just forgot to specify implemented interface: ": INotifyPropertyChanged"
your class Partner may be INotifyPropertyChanged (I put you code for Title, and this is the same for other parameters of your object)
public class Partner : INotifyPropertyChanged
{
private string title;
public string Title
{
get { return this.title; }
set
{
if (this.title != value)
{
this.title = value;
this.NotifyPropertyChanged("Title");
}
}
}
public override string ToString()
{
return Title;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}

Binding IsEnabled to a property of a custom type collection and refresh trigger

I have a window with a listbox containing several items. There is a itemtemplate which defines they should be represented by a checkbox:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:converters="clr-namespace:WpfApplication1.Converters"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Customers}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Path=Item.Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"
HorizontalAlignment="Right"
Margin="5"
Content="Test"
IsEnabled="{Binding Customers, Converter={converters:CustomersToBoolConverter}}"
/>
</Grid>
</Window>
Next, I declare the binded 'Customers' collection and its items in the code behind like so:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<CheckedListItem<Customer>> Customers { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
Customers = new ObservableCollection<CheckedListItem<Customer>>();
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Kelly Smith" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Joe Brown" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Herb Dean" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "John Paul" }));
DataContext = this;
}
void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
}
}
Here is the Customer class definition:
public class Customer
{
public string Name { get; set; }
}
Here is the definition of the CheckedListItem class:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
This latter implements my INPC logic.
I bind the status of my button to the Customers list and the output of a converter which returns true if any of the items is checked.
it works well when loading the window, however, if I check any item of the list, nothing happens. Moreover, if I implement the INPC logic in the class MainWindow and explicitly call
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
Then, the notification is sent to my window and the status of the button is refreshed just fine.
My question is how can I send this notification from within the CheckedListItem, is it possible?
Code source : http://www.jarloo.com/how-to-create-a-checkedlistbox-in-wpf/
You can bind to CollectionChanged event of ObservableCollection which Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
You can put the event subscription code in MainWindow Constructor
Customers.CollectionChanged += Customers_CollectionChanged;
And handler in code behind :
void Customers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/// Your code
}

A lot of textboxes, how to manage? MVVM WPF

I writing some App, to input 64 byte for sending over some communication standard. App must have in each textbox one byte, so I need 64 textboxes. This means that I must make 64 "NotifyPropertyChanged" getter/setter functions. Or is any better way?
If is no better way, how to manage all this functions? Paste only this 64 functions in special class file and connect to my viewmodel? And how to connect it? As instance or something else?
In total I will need 64 textboxes for sending and then also 64 (textblocks) for received message. Yes is possible to make everything in one textbox but I am not making decisions here :)
I also need restrict characters inputet into textboxes - 0-9 and A-F.
I am new in MVVM and WPF so sorry if this is stupid question. If any other question please ask.
Bindings support arrays, so just add an array in your view model:
public class MyViewModel
{
private int[] _Vals = new int[64];
public int[] Vals { get { return this._Vals; } }
}
And bind each textbox to an element in your XAML:
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Vals[0], UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Vals[1], UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Vals[2], UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Vals[3], UpdateSourceTrigger=PropertyChanged}" />
... etc ...
</StackPanel>
Consider making a ViewModel for one byte (containing the implementation of INotifyPropertyChanged) and adding 64 instances of this to a collection.
Then you can use a ItemsControl to show the TextBoxes and use the collection to get all the bytes.
To restrict the textboxes to allow for Hex values only you can use a combination of a ValueConverter and handeling the keydown on the textboxes.
public partial class MainWindow : Window
{
private const int numberOfBytes = 64;
private NumbersViewmodel numbers = new NumbersViewmodel(numberOfBytes);
public MainWindow()
{
InitializeComponent();
this.DataContext = numbers;
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public class NumbersViewmodel : ViewModelBase
{
private ObservableCollection<NumberViewModel> numbers;
public ObservableCollection<NumberViewModel> Numbers
{
get { return numbers; }
set
{
if (numbers != value)
{
numbers = value;
OnPropertyChanged("Numbers");
}
}
}
public ICommand SendNumbers
{
get;
private set;
}
public NumbersViewmodel(int numberOfNumbers)
{
sendNumbers = new SimpleCommand(SendNumbersExecuted);
this.Numbers = new ObservableCollection<NumberViewModel>();
for (int i = 0; i < numberOfNumbers; i++)
{
this.Numbers.Add(new NumberViewModel());
}
}
private void SendNumbersExecuted()
{
///DoSomethingWith(Numbers);
}
}
public class NumberViewModel : ViewModelBase
{
private byte number;
public byte Number
{
get { return number; }
set
{
if (number != value)
{
number = value;
OnPropertyChanged("Number");
}
}
}
}
public class SimpleCommand : ICommand
{
private Action execute;
public SimpleCommand(Action execute)
{
this.execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (execute != null && CanExecute(parameter))
{
execute();
}
}
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Numbers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Number}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1"
Command="{Binding SendBytes}">Send the bytes</Button>
</Grid>

Update combobox while using DisplayMemberPath

I am using WPF and MVVM light framework (and I am new in using them)
Here is the situation:
I have a combobox displaying a list of items (loaded from a database) and I am using the DisplayMemberPath to display the title of the items in the combobox.
In the same GUI, the user can modify the item title in a text box. A button 'Save' allows the user to save the data into the database.
What I want to do is when the user clicks 'Save', the item title in the combobox gets updated too and the new value is displayed at that time. However, I do not know how to do that...
Some details on my implementation:
MainWindow.xaml
<ComboBox ItemsSource="{Binding SourceData}" SelectedItem="{Binding SelectedSourceData,Mode=TwoWay}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
MainViewModel.xaml
public class MainViewModel:ViewModelBase
{
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{return _selectedFoo;}
set{_selectedFoo=value; RaisePropertyChanged("SelectedSourceData"); }
}
public string SelectedDataInTextFormat
{
get{return _selectedDataInTextFormat;}
set{_selectedDataInTextFormat=value; RaisePropertyChanged("SelectedDataInTextFormat");
}
}
I would appreciate if anyone could help me on this one.
Thanks for your help.
Romain
You might simply update the SelectedSourceData property when SelectedDataInTextFormat changes:
public string SelectedDataInTextFormat
{
get { return _selectedDataInTextFormat; }
set
{
_selectedDataInTextFormat = value;
RaisePropertyChanged("SelectedDataInTextFormat");
SelectedSourceData = SourceData.FirstOrDefault(f => f.Title == _selectedDataInTextFormat)
}
}
EDIT: In order to change the Title property of the currently selected Foo item in the ComboBox, you could implement INotifyPropertyChanged in your Foo class:
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string title = string.Empty;
public string Title
{
get { return title; }
set
{
title = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
}
Then simply set the Title property of the selected item:
SelectedSourceData.Title = SelectedDataInTextFormat;
There is many ways to do this, This example takes advantage of the Button Tag property to send some data to the save button handler(or ICommand), Then we can set the TextBox UpdateSourceTrigger to Explicit and call the update when the Button is clicked.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="105" Width="156" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<StackPanel Name="stackPanel1">
<ComboBox x:Name="combo" ItemsSource="{Binding SourceData}" DisplayMemberPath="Title" SelectedIndex="0"/>
<TextBox x:Name="txtbox" Text="{Binding ElementName=combo, Path=SelectedItem.Title, UpdateSourceTrigger=Explicit}"/>
<Button Content="Save" Tag="{Binding ElementName=txtbox}" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Foo> _sourceData = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
SourceData.Add(new Foo { Title = "Stack" });
SourceData.Add(new Foo { Title = "Overflow" });
}
public ObservableCollection<Foo> SourceData
{
get { return _sourceData; }
set { _sourceData = value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var txtbx = (sender as Button).Tag as TextBox;
txtbx.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class Foo : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged("Title"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Code:
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{
return _selectedFoo;
}
set{
_selectedFoo=value;
RaisePropertyChanged("SelectedSourceData");
}
}
public string SelectedDataInTextFormat //Bind the text to the SelectedItem title
{
get{
return SelectedSourceData.Title
}
set{
SelectedSourceData.Title=value;
RaisePropertyChanged("SelectedDataInTextFormat");
}
}
XAML:
<ComboBox ItemsSource="{Binding SourceData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedSourceData,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

INotifyPropertyChanged.PropertyChanged always NULL

I know I am doing something wrong here but what. Please have a look and point out my error.
I will see "Peter" in my textbox but no "Jack" after the button click.
My class
namespace App
{
class Person : INotifyPropertyChanged
{
private string name;
public String Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
public Person()
{
Name = "Peter";
}
public void SetName(string newname)
{
Name = newname;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
}
My XAML
<Window x:Class="test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
Title="MainWindow" Height="400" Width="400">
<Grid>
<Grid.Resources>
<app:Person x:Key="person"/>
</Grid.Resources>
<TextBox Width="100" Height="26" Text="{Binding Source={StaticResource person}, Path=Name, Mode=TwoWay}" />
<Button Content="Button" Height="23" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
And my codebehind
public partial class MainWindow : Window
{
Person person;
public MainWindow()
{
InitializeComponent();
person = new Person();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
person.SetName("Jack");
}
}
Thanks.
You have two instances of Person. PropertyChanged is not null in the static resource
This isn't really what StaticResources are for. Get rid of the static resource, change the binding to:
{Binding Path=Name, Mode=TwoWay}
and add this to your constructor:
DataContext = person;
That object person in codebehind of MainWindow is not the same object you have binding to in XAML
If you want to use that object from resources you have to find it in code behind so something like this in constructor
person = (Person)Resources["person"];

Resources