Please Checkout this Pictute
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public string newPerson(string Value)
{
this.Name = Value;
return "";
}
public Person(string value)
{
this.name = value;
}
public string Name
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
.
XAML:
<Window.Resources>
<local:Person x:Key="NewPerson" Name="shuvo"/>
<ObjectDataProvider x:Key="AddNewPerson" ObjectType="{x:Type local:Person}" MethodName="newPerson">
<ObjectDataProvider.MethodParameters>
<sys:String>yahoo</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<TextBlock Height="23" HorizontalAlignment="Left" Margin="46,57,0,0" Name="textBlock1" Text="{Binding Source={StaticResource NewPerson},Path=Name}" VerticalAlignment="Top" Width="207" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="46,149,0,0" Name="textBox1" VerticalAlignment="Top" Width="234" Text="{Binding Source={StaticResource AddNewPerson}, Path=MethodParameters[0],BindsDirectlyToSource=True,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}" />
</Grid>
The ObjectDataProvider is creating a new instance of the Person class and then calling the newPerson method of that new instance. This new instance is not connected to the already existing Person instance that you declared in the window resources as NewPerson. Therefore the object data provider is calling a method that has no effect.
You should mofify the ObjectDataProvider to use the ObjectInstance property and bind it to the windows resources defined NewPerson. See here for more information.
Related
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.
Please help, I was trying to do this small example.
My aim is to when I keep the checkbox ticked the app should show the the Ip address of the Host I enter. But The checkbox IsChecked property is never updated in the view model, Even it is been changed in the UI
My View `
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.00" Color="LavenderBlush" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<StackPanel Grid.Row="0" Margin="150,30,69,236" Grid.ColumnSpan="2">
<TextBox x:Name="inputBox" Text="{Binding TxtHostName, Mode=TwoWay}" Foreground="Azure" Background="YellowGreen" VerticalAlignment="Bottom" Height="45"/>
</StackPanel>
<Button Command="{Binding StartCommand }" Content="Get IP" HorizontalAlignment="Left" Margin="257,89,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.013,-0.273"/>
<TextBlock Text="{Binding IpAddress}" Background="BlueViolet" Margin="150,153,69,104" Grid.ColumnSpan="2" />
<Button Content="Close" Command="{Binding CloseCommand}" HorizontalAlignment="Left" Margin="257,250,0,0" VerticalAlignment="Top" Width="75"/>
<CheckBox Content="CheckBox" IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left" Margin="150,111,0,0" VerticalAlignment="Top"/>
</Grid>
`
My ViewModel:
public class ViewModel:INotifyPropertyChanged
{
#region INPC
public void RaisePropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private string txtHostName;
public string TxtHostName
{
get { return txtHostName; }
set { txtHostName = value;
RaisePropertyChanged("TxtHostName");
}
}
private string ipAddress;
public string IpAddress
{
get { return ipAddress; }
set { ipAddress = value;
RaisePropertyChanged("IpAddress");
}
}
private bool checkbox;
public bool CheckBox
{
get { return checkbox; }
set { checkbox = value;
RaisePropertyChanged("IsSelected");
}
}
public event EventHandler RequestClose;
protected void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
private RelayCommand _StartCommand;
public ICommand StartCommand
{
get
{
if (this._StartCommand == null)
this._StartCommand = new RelayCommand(StartClick);
return this._StartCommand;
}
}
private RelayCommand _CloseCommand;
public ICommand CloseCommand
{
get
{
if(this._CloseCommand==null)
this._CloseCommand=new RelayCommand(CloseClick);
return this._CloseCommand;
}
}
private void CloseClick(object obj)
{
OnRequestClose();
}
private void StartClick(object obj)
{
if (checkbox)
{
string HostName = TxtHostName;
IPAddress[] ipaddress = Dns.GetHostAddresses(HostName);
foreach (IPAddress ipaddr in ipaddress)
{
IpAddress = ipaddr.ToString();
}
}
else
{
IpAddress = "Please tick the checkbox";
}
}
}
}
The RealyCommand is as it should be.
The CheckBox Property value never changes weather I change it in the UI or not.
Your raising your property changed event against IsSelected, but your bindable property is called Checkbox, rename Checkbox to IsSelected and update your private variable to something like isSelected.
In this case Id rename the variable to IsChecked or ComboBoxIsChecked.
I'm not sure if there is a copy-and-paste error but your View Model property is called Checkbox while you are raising the property changed event using the label IsSelected.
This and the error in the binding might be your problem. Based on your View Model the binding should be:-
<CheckBox Content="CheckBox" IsChecked="{Binding Checkbox, Mode=TwoWay}" HorizontalAlignment="Left" Margin="150,111,0,0" VerticalAlignment="Top"/>
Update: Recommendation if you are using C# 5.0 or above
To avoid typo's when creating setters and raising IPropertyNotifyChange events I would recommend using the CallerMemberName attribute as follows:-
public void RaisePropertyChanged([CallerMemberName] string propName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Then your setter in your example becomes:-
private bool checkbox;
public bool CheckBox
{
get { return checkbox; }
set { checkbox = value;
RaisePropertyChanged();
}
}
Meaning as you refactor your View Model then the compiler will insert the name of the calling property to ensure the label in the INotifyProertyChanged event matches your property name without you having to remember to manually update it yourself.
i am trying to bind the text of a textbox to a property in my class, and it is not working, I am editing the property in the code behind but I don't see the string in the textbox
this is the class, and the property i am trying to bind is called songFolder.
public class song : INotifyPropertyChanged
{
public string title {get; set; }
public string artist { get; set; }
public string path { get; set; }
public static string folder;
public string songsFolder { get { return folder; } set { folder = value; NotifyPropertyChanged("songsFolder"); } }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public song()
{
}
public song(string title, string artist, string path)
{
this.title = title;
this.artist = artist;
this.path = path;
}
}
and the xaml, containing the resource and the textbox wich i am tring to bind
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Song Filler" Height="455" Width="525">
<Window.Resources>
<local:song x:Key="song"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Name="browseBox" Text="{Binding Source={StaticResource ResourceKey=song}, Path=songsFolder, Mode=TwoWay}" Grid.Column="0"></TextBox>
<Button Grid.Column="1" Width="auto" Click="Browse">browse</Button>
</Grid>
--------------update----------------
I added the next line to ctor of the window:
BrowseBox.DataContext=new song()
And while debugging I saw that the property is changing but the text in the textbox isn't.
The string passed into the NotifyPropertyChanged event should be the same name of the property itself.
public string songsFolder
{
get
{
return folder;
}
set
{
folder = value;
NotifyPropertyChanged("songsFolder");
}
}
Also,
try adding UpdateSourceTrigger="PropertyChanged" to the binding of the textBox
<TextBox Name="browseBox" Text="{Binding Source={StaticResource ResourceKey=song}, Path=songsFolder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0"></TextBox>
Edit: Maybe the DataContext is not getting set correctly. You can also try this method (W/out a static Key)
Code behind, inside the Ctor of the window:
browseBox.DataContext = new song();
Then, update textBox finding to:
<TextBox Name="browseBox" Text="{Binding Path=songsFolder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0"></TextBox>
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"];
I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?