IDataErrorInfo does not fire when binding to a complex object - wpf

I have a simple dialog that contains edit boxes such as this:
<TextBox Text="{Binding Path=EmailSettings.SmtpServer, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
The dialog uses a Model as its data context (to simplify the model example INotifyPropertyChanged has not been shown nor is the code that creates the model and
sets the dialog data context to the model instance):
class EmailSettingsModel : IDataErrorInfo
{
public EmailSettingsModel ()
{
EmailSettings = new EmailSettings();
}
public EmailSettings EmailSettings
{ get; set; }
string _error;
public string Error
{
get { return _error; }
set { _error = value; }
}
public string this[string propertyName]
{
get
{
string errorMessage = null;
if ( string.Compare( propertyName, "EmailSettings.SmtpServer" ) == 0 )
{
if ( !string.IsNullOrWhiteSpace( EmailSettings.SmtpServer ) )
errorMessage = "SMTP server is not valid";
}
Error = errorMessage;
}
}
}
The model contains a property that is a simple POCO class that has several properties on it.
class EmailSettings
{
public string SmtpServer
{ get; set; }
}
I could not get the IDataErrorInfo indexer to fire and spent hours looking. When I changed the binding on the text box to use a simple property:
<TextBox Text="{Binding Path=SmtpServer, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
on the Model as below the IDataErrorInfo indexer fired.
class EmailSettingsModel
{
public string SmtpServer
{ get; set; }
}
Was IDataErrorInfo not called because I used a compound property for the binding statement. I have used complex properties like this for normal data binding and they work but for this example IDataErrorInfo was not called.

IDataErrorInfo fires only at the level where implemented
For example if you have Binding Path looking like this "viewModel.property1.property2.property3" you will need to implement IDataErrorInfo inside the class of viewModel and inside the class of property1 and inside the class of property2. Property3 is a string.
So in order to make it work for you just implement IDataErrorInfo anywhere else.

Related

Updating values in ObservableCollection

Hey I have an ObservableCollection which consists of a class with two attributes (strings = User and Response) bound to a listbox.
I would like to have the users in the listbox first, which I add with this:
for (int i = 0; i < ArrStrUser.Length; i++)
{
Users.Add(new User() { input = ArrStrUser[i].Trim() });
}
I want to add the responses to the respective user later.
If I do this, they will be added to the ObservableCollection but not update in the listbox.
Users[i].response = strOutput.Trim().Replace(Environment.NewLine, " ");
The ObservableCollecton
private ObservableCollection<Input> Users = new ObservableCollection<Input>();
The Class:
public class Input
{
public string user{ get; set; }
public string response { get; set; }
}
XAML:
<ListBox x:Name="LBresponse" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" />
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path= user}" Width="50"/>
<TextBlock Text="{Binding Path= response}" />
<Button Content="Delete" Click="DeleteUser_Clicked" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
Simple solution
Your Input class needs to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event upon changing property's value in order to update the ListBox. The ObservableCollection only "cares" about adding or removing items, it doesn't handle item's property changing.
Try editing your input class like this:
public class Input : INotifyPropertyChanged
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set {
_response = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now changing the Response property should update the UI.
Better solution
I'd also advise you to separate the INotifyPropertyChanged implementation into its own class if you want to use it somewhere else, too. Or better yet, use a library that already has it, like the mvvm-helpers nuget package by James Montemagno.
Here's a link to the INotifyPropertyChanged implementation from that library
This is how you use it:
public class Input : ObservableObject
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set => SetProperty(ref _response, value);
}
}
It also supports passing in an OnChanged Action and a validation function.

Validation and NotififyOnValidation Error don't work

I have a property Name which is required in the model. Then in the VM I have a Name property that access the one of the model. And in my view I bind a textbox with the Name property of the VM. But the textbox does not get in red when it is empty. I do get the error : Field Name is required. But once again the textbox does not turn red. Please help
here is the model :
public class Task : BasicAuditTrail
{
[Required]
[MaxLength(256)]
public string Name { get; set; }
}
Here is the VM :
public class TaskManagerViewModel : ViewModelBase, ITaskManagerViewModel
{
public Task CurrentTask => taskManager.CurrentTask;
public string Name
{
get
{
return CurrentTask.Name;
}
set
{
CurrentTask.Name = value;
IsDirty = true;
RaisePropertyChanged();
}
}
The View :
<TextBox Grid.Row="0" Grid.Column="3" Name="Name" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
As mentioned in comments, the Name property of CurrentTask should be directly bind to the Textbox. This makes the error to work correctly.
To set the IsDirty property on the viemwodel, subscribe to the PropertyChanged event of the CurrentTask and set IsDirty there!
PS: make sure to remove the event when the CurrentTask is changing so as to not have any memory leaks

How to bind XamDataGrid to Model Properties through ViewModel

In my C#/WPF app, I and am trying to bind an Infragistics WPF XamDataGrid to an ObservableCollection. MyViewModel exposes the Model property. When I add the property whose value I want displayed to MyViewModel, everything works fine. Below shows an example where I'm binding to the UnitID property exposed by an instance of a MyViewModel contained in the UnitsOfMeasure ObservableCollection.
If I change "Name" to "Model.Name", at runtime the correct number of rows appears in the data grid, but all fields are blank. No runtime error is raised.
My workaround is to add each property to MyViewModel and reference the property in the Model in each getter and setter. Makes for redundant code, and doesn't agree with Laurent's demo's.
This xaml works:
<igEditors:XamDataGrid Theme="Royale" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch" Margin="2" Name="xamDataGridUnitsOfMeasure" DataSource="{Binding Path=UnitsOfMeasure}" VerticalAlignment="Stretch">
<igEditors:XamDataGrid.FieldLayoutSettings>
<igEditors:FieldLayoutSettings AutoGenerateFields="False"/>
</igEditors:XamDataGrid.FieldLayoutSettings>
<igEditors:XamDataGrid.FieldLayouts>
<igEditors:FieldLayout IsDefault="True">
<igEditors:FieldLayout.Fields>
<igEditors:Field Name="UnitID" Label="UOM ID">
<igEditors:Field.Settings>
<igEditors:FieldSettings AllowEdit="False"/>
</igEditors:Field.Settings>
</igEditors:Field>
.
.
.
Changing UnitID to Model.UnitID does not work:
<igEditors:XamDataGrid Theme="Royale" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch" Margin="2" Name="xamDataGridUnitsOfMeasure" DataSource="{Binding Path=UnitsOfMeasure}" VerticalAlignment="Stretch">
<igEditors:XamDataGrid.FieldLayoutSettings>
<igEditors:FieldLayoutSettings AutoGenerateFields="False"/>
</igEditors:XamDataGrid.FieldLayoutSettings>
<igEditors:XamDataGrid.FieldLayouts>
<igEditors:FieldLayout IsDefault="True">
<igEditors:FieldLayout.Fields>
<igEditors:Field Name="Model.UnitID" Label="UOM ID">
<igEditors:Field.Settings>
<igEditors:FieldSettings AllowEdit="False"/>
</igEditors:Field.Settings>
</igEditors:Field>
.
.
.
Here's part of the ViewModel that is functional:
public class UnitOfMeasureViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the UnitOfMeasureViewModel class.
/// </summary>
public UnitOfMeasureViewModel(UnitOfMeasure model)
{
Model = model;
//Model.PropertyChanged += (s, e) =>
// {
// RaisePropertyChanged(e.PropertyName);
// };
}
public UnitOfMeasure Model
{
get;
private set;
}
public string UnitID
{
get
{
return Model.UnitID;
}
set
{
Model.UnitID = value;
}
}
The nested binding is supported by XamGrid control - http://help.infragistics.com/Help/NetAdvantage/WPF/2012.1/CLR4.0/html/xamGrid.html
Or
Refer to this link of Infragistics help -http://www.infragistics.com/community/forums/t/67603.aspx
This will help you do the nested binding
Else =>
Do you want to extract interface out of Model object class and try to implement it at the class holding Model as property?
public interface IModel
{
public string UnitId { get; set; }
}
public class Model : IModel
{
private string unitId;
public string UnitId
{
get { return unitId; }
set { unitId = value; }
}
}
public class ModelParent : IModel
{
private Model model;
public string UnitId
{
get { return model.UnitId; }
set { model.UnitId = value; }
}
}

Binding to Complex Objects in the ViewModel from the View?

Say for example I have the following type:
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
The above type can be assigned to be held in a Propety in a ViewModel like so assuming a corresponding backing field has been created but omitted here ofc:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
// raise property changed etc
}
}
In my xaml a straight forward binding would be:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
Text="{Binding MessageToDisplay,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Can you extend a binding by using the dot notation syntax? e.g:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
**Text="{Binding SelectedSite.Name,**
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Seems like a an interesting feature but my gut instinct is a no as my DC is being assigned at RunTime so at DesignTime or CompileTime, I can't see any clues that could make this feature work or not?
Correct me if have misunderstood what a complex object is, I have simplified mine down for the sake of this question.
Of course this is possible. However, WPF needs to know when any property along the path has changed. To that end, you need to implement INotifyPropertyChanged (or other supported mechanisms). In your example, both Site and the VM containing SelectedSite should implement change notification).
Here's how you could implement the functionality you specified in your question:
// simple DTO
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
// base class for view models
public abstract class ViewModel
{
// see http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-viewmodel.html for an example
}
public class SiteViewModel : ViewModel
{
private readonly Site site;
public SiteViewModel(Site site)
{
this.site = site;
}
// this is what your view binds to
public string Name
{
get { return this.site.Name; }
set
{
if (this.site.Name != value)
{
this.site.Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
// other properties
}
public class SitesViewModel : ViewModel
{
private readonly ICollection<SiteViewModel> sites;
private SiteViewModel selectedSite;
public SitesViewModel()
{
this.sites = ...;
}
public ICollection<SiteViewModel> Sites
{
get { return this.sites; }
}
public SiteViewModel SelectedSite
{
get { return this.selectedSite; }
set
{
if (this.selectedSite != value)
{
this.selectedSite = value;
this.OnPropertyChanged(() => this.SelectedSite);
}
}
}
}
And your view might look something like this (assuming a DataContext of type SitesViewModel):
<ListBox ItemsSource="{Binding Sites}" SelectedItem="{Binding SelectedSite}"/>
Below is what worked for me:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
RaisePropertyChanged("SelectedSite");
}
}
In my xaml I was able to do:
<TextBox Name="tbSiteName"
Width="250"
Height="30"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsReadOnly="True"
Style="{StaticResource MainTextBoxStyle}"
Text="{Binding SelectedSite.Name,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
This allows you to access data members off the Site Type without having to create individual properties that wrap each data member on the Site Type. Then individual controls can bind to each property declared in the VM. In a one to one fashion, this aproach can become rather verbose. The binding extension attached to the Text property of the TextBox control shown above, shows that we are not binding to a simple straight forward property but actually to a custom type. Potentially removing the need to create more public properties.

INotifyPropertyChanged in subclass

I want to bind a TextBox in the window to a property contained within a class that is a variable of the viewmodel and ensure that INotifyPropertyChanged's PropertyChanged event propagates from the class to the parent.
Let me illustrate with an example:
(Window's DataContext is set to an instance of ViewModel)
public class ViewModel {
private OtherClass classInstance = new OtherClass();
public int Attribute {
get { return classInstance.Attribute; }
}
}
public class OtherClass : INotifyPropertyChanged {
private int _attribute;
public int Attribute {
get { return _attribute; }
set {
_attribute = value;
PropertyChanged("Attribute");
}
}
...
}
The problem in this example is that, when Attribute changes, the bound Textbox does not update the binding since I assume it's listening to the ViewModel's PropertyChanged event and not that of the instance of OtherClass.
Any ideas on how to remedy this situation? I was thinking about chaining OtherClass's INotifyPropertyChanged to that of its parent, but there has to be a better way.
Why not bind directly to the class property instead of using a proxy?
public class ViewModel {
private OtherClass classInstance = new OtherClass();
public OtherClass MyOtherClass {
get { return classInstance; }
}
}
Then in your binding you can simply reference the property via the SubClass
{Binding MyOtherClass.Attribute}
A drop dead simple example, but it works!
The Code Behind:
public partial class MainWindow : Window {
private readonly SomeClass _someClass = new SomeClass();
public MainWindow() {
InitializeComponent();
DataContext = _someClass;
}
}
public class SomeClass: INotifyPropertyChanged {
private readonly SomeSubClass _mySubClass = new SomeSubClass();
public SomeSubClass MySubClass {
get { return _mySubClass; }
}
private String _name;
public String Name {
get { return _name; }
set {
_name = value;
OnPropertyChanged("Name");
}
}
//Property Change Stuff
}
public class SomeSubClass : INotifyPropertyChanged {
private String _name;
public String Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
//Property Change Stuff
}
The XAML:
<Window x:Class="JWC.Examples.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<StackPanel>
<Label Content="Name" VerticalAlignment="Top" />
<TextBox Text="{Binding Name}" />
<Label Content="SubClass.Name" />
<TextBox Text="{Binding MySubClass.Name}" />
<Label Content="Bound to Name" />
<TextBlock Text="{Binding Name}" />
<Label Content="Bound to MySubClass.Name" />
<TextBlock Text="{Binding MySubClass.Name}" />
</StackPanel>
</Window>
You will need to do something like this:
public class ViewModel {
public ViewModel() {
classInstance = new OtherClass();
classInstance.PropertyChanged += HandleAttributeChanged;
}
private void HandleAttributeChanged(object Sender, NotifyPropertyChangedEventArgs args) {
PropertyChanged("Attribute");
}
}
I don't show it here, but you should also implement IDisposable and unsubscribe from the PropertyChanged event on your child, otherwise you will leak memory.
Alternatively you can expose the classInstance as a public property and set your binding to: ViewModel.classInstance. Note this needs to be a property and not the field itself.
I think the parent class should subscribe to the child propertyCahnged event..something like:
public class ViewModel
{
private OtherClass classInstance = new OtherClass();
public ViewModel()
{
classInstance.PropertyChanged += NotifyChildAttributeChanged;
}
public int Attribute
{
get { return classInstance.Attribute; }
}
}
NotifyChildAttributeChanged is basically a method that listens only to the "Attribute" property and fires a PropertyChanged notification of its own..
Of course our parent class must implement INotifyPropertyChanged as well as will all ViewModels (preferably) and your DataContext will detect the change.
To get around this you need to implement INotifyPropertyChanged on your view model as well. Just add the interface and the event and the rest will take care of itself - no need to chain the events / calls together.
Check out this for using reflection to get the property as well.
http://tsells.wordpress.com/2011/02/08/using-reflection-with-wpf-and-the-inotifypropertychanged-interface/

Categories

Resources