Validation between multiple fields in different levels - wpf

I have a problem with validations between multiple fields. For example, I have a ViewModel named RangeDateViewModel that contains 2 instances of a class named DateViewModel - they represent a start date and an end date respectively.
So my binding looks like this:
<TextBox Text="{Binding StartDate.Date, ValidateOnDataError=True}">
<TextBox Text="{Binding EndDate.Date, ValidateOnDataError=True}">
My RangeDateViewModel class implements the IDataErrorInfo interface.
In my plan, the RangeDateViewModel would validate that the start date is before the end date, by applying the validation logic in the IDataErrorInfo["propertyName"] function like this:
public string this[string columnName]
{
get
{
return ValidationError();
}
}
The problem is that this is never being called, and instead the IDataErrorInfo properties that reside in each of the DateViewModel classes are being called instead.
I guess this is because the bound property is not in the same level of RangeDateViewModel, but instead inside the child DateViewModel.
I think my need is quite basic and there must be an easy solution for this problem.
I tried using ValidationRules instead of IDataErrorInfo but then I'd problems letting the ViewModel know of the current validation status from the ValidationRules.

Try using the following approach:
Create a DataTemplate for DateViewModel:
<DataTemplate DataType="{x:Type ViewModels:DateViewModel}">
<TextBox Text="{Binding Date}">
</DataTemplate>
Bind the instances of this ViewModel to a ContentControl and set ValidateOnDataError to true on that binding:
<ContentControl Content="{Binding StartDate, ValidateOnDataError=True}" />
<ContentControl Content="{Binding EndDate, ValidateOnDataError=True}" />
In RangeDateViewModel subscribe to the PropertyChanged event of StartDate and EndDate and when raised, raise a PropertyChanged event with StartDate / EndDate:
StartDate.PropertyChanged += (s, e) => InvokePropertyChanged("StartDate");
EndDate.PropertyChanged += (s, e) => InvokePropertyChanged("EndDate");

I had the problem that public string this[string columnName] was simply not called just the other week.
The solution was simple.
The binding WPF binding engine could not follow the nesting of my ViewModels.
I had assumed that I needed to implement the property in the ViewModel that is the current DataContext, but instead it needs to be implemented in the ViewModel that is bound to the control.
Example:
<TextBox Text="{Binding Path=ProductViewModel.DescriptionViewModel.ProductName,
Mode=TwoWay,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}" />
Here DescriptionViewModel is the class that contains the bound property. IDataErrorInfo needs to be implemented in that class (not in ProductViewModel or another class up the hierarchy that may contain it) then everything will work fine.

Related

DataTemplateSelector and update binding manually

I am using a DataTemplateSelector to swap input method for user based on whether he wants to enter text or pick a date value. Which means the selector switches between a TextBox and a DatePicker. Each control must use explicit way to update binding source. To sum up the user could pick a date or he could enter a text and once he is done he may click on apply button to update sources. Though only apply button updates the souce and not on focus lost.
The owner control of the DataTemplateSelector is a custom ContentControl called InputControl which is futhermore part of a UserControl.
Here is a small piece of pseudocode just to visualize things better:
public class InputControl : ContentControl
{
//// this method shall be executed once user clicks on apply button
//// inside this method the source of binding shall be updated no matter what input method used chose
public void Update()
{
}
}
Xaml looks kinda like this:
<UserControl>
<UserControl.Resources>
<DataTemplate x:key="text">
<TextBox Text="{Binding Mode=TwoWay, Path=., UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
<DataTemplate x:key="date">
<DatePicker DateValue="{Binding Mode=TwoWay, Path=., UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
<MyDataTemplateSelector x:key="myDataTemplateSelector"
TextTemplate="{StaticResource text}"
DateTemplate="{StaticResource date}">
</MyDataTemplateSelector>
</UserControl.Resources>
<Inputcontrol Content="{Binding Path=., Mode=TwoWay}" ContentTemplateSelector="{StaticResource myDataTemplateSelector}" />
</UserControl>
The selector looks like this
Public class MyDataTemplateSelector : DataTemplateSelector
{
Public DataTemplate TextTemplate { get; set;}
Public DataTemplate DateTemplate { get; set;}
Public DataTDemplate Select(.....)
{
....
}
}
Now the problem is how do I update the binding source from InputControl no matter what control is selected inside the template? If you read the comments above the method InputControl.Update() you will understand better what I mean with user updating source no matter what template.
If its TextBox selected the user shall be able to just call InputControl.Update() and it will update textbox binding source. If its DatePicker the user shall be able to do the same which is only to call InputControl.Update(). The source will get updated and Inputcontrol.Update() is a central point to trigger updating process no matter what control.
To sum up the method Update() is pretty central and updates the binding source no matter if its TextBox or DatePicker.
How do I do that?

Validation against Linq Entity properties

I have a linq entity wrapped in a property that I have in my ViewModel:
public NA_Header Na_Header
{
get
{
_na_header=Job.NA_Headers.FirstOrDefault();
return _na_header;
}
set { _na_header = value; }
}
I use this Na_Header property in my view to update fields in my GUI (xaml):
<Toolkit:DateTimePicker BorderBrush="Gray" Grid.Column="1" Grid.Row="1" Margin="5" VerticalAlignment="Top" Value="{Binding Path=Na_Header.JobStart, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, ValidatesOnDataErrors=True}" Format="Custom" FormatString="MMM dd yyyy"/>
<Toolkit:DateTimePicker BorderBrush="Gray" Grid.Column="1" Grid.Row="2" Margin="5" VerticalAlignment="Top" Value="{Binding Path=Na_Header.JobEnd, Mode=TwoWay, Converter={StaticResource thisNullDateConverter},ValidatesOnDataErrors=True}" Format="Custom" FormatString="MMM dd yyyy"/>
Notice, I have 2 properties in this object that I want to validate against Na_Header.JobStart and Na_Header.JobEnd (these are dates). I have the ValidatesOnDataErrors set to true on each property.
Also in the ViewModel I have IDataError implemented:
public string this[string columnName]
{
get
{
if (columnName == "Na_Header")
{
if (Na_Header.JobStart > Na_Header.JobEnd)
return "Job Stat Date must be an earlier date than Job End Date";
}
return "";
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
Unfortunately, the DataError does not fire when changes are made to the JobStart or JobEnd properties. I don't think I need NotifyPropertyChanged because it is a linq entity (which the PropertyChanged stuff is done automatically)
Any clues?
But you don't have a Linq entity as you put it.
IEnumerable First() is actually returning an object of type T, although you don't show your definition for field _na_header. I'd guess at that being a Job/Na_Job.
I would recommend that you have a couple of choices.
If your Job class is part of your ViewModel namespace (or you just want to get updates when it changes even in your Model layer), then implement INotifyPropertyChanged. This way the JobStart and JobEnd will update the Binding.
Instead of exposing the NA_Header property, why not expose JobStart and JobEnd instead (and then implement INotifyPropertyChanged in your ViewModel, but I think you have already). This may simplify your ViewModel by hiding some of the classes in the Model tier, or it may make it more complicated, but at the benefit of simplified XAML.

EF EntityObject not updating databindings of relationships

I'm using EntityFramework, WPF and MVVM in my application and got some problems with updating the databinding of relationships between EntityObjects. I was able to downsize my problem to only a few lines of XAML and I hope someone can help me as I'm still not very confident with EF and MVVM.
Anyway, here we go with the simplified XAML:
<DatePicker Grid.Row="2" Grid.Column="1"
SelectedDate="{Binding Path=File.SentDate,
StringFormat={}{0:dd/MM/yyyy}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" IsEnabled="{Binding Path=IsEnabled}"/>
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Contacts}" DisplayMemberPath="Name"
SelectedItem="{Binding Path=File.Sender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"
VerticalAlignment="Center">
</ComboBox>
<Label Content="{Binding Path=File.SenderId}" Grid.Row="4"/>
<Label Content="{Binding Path=File.Sender.Name}" Grid.Row="5"/>
<Label Content="{Binding Path=File.SentDate}" Grid.Row="6"/>
I'm using the last 3 Labels to test my databinding. Changing the File.SentDate using the DatePicker updates the databinding to the last Label without problem.
Now File is of type EntityObject and has a SenderId property of type GUID. It also has a relationship to my Contacts through the Sender property. Obvisouly, SenderId is the GUID of the corresponding Contact EntityObject which is related to File through the Sender relationship. A File can have only 1 single Sender of type Contact.
Anyway, what happens is that when I select another sender using the combobox, the Label displaying the File.SenderId property get properly updated. However, the one with the File.Sender.Name property i.e. the one using the reléationship does not get updated.
So I'm guessing that there is something special about updating the databinding of relationships in EF.
Can someone please suggest a solution to this?
Unfortunately, the Entity Framework doesn’t notify when an association property changes. That’s the reason why your Binding didn’t work.
The issue is reported to Microsoft: http://connect.microsoft.com/VisualStudio/feedback/details/532257/entity-framework-navigation-properties-don-t-raise-the-propertychanged-event
Another workaround is shown by the BookLibrary sample application of the WPF Application Framework (WAF). The Book class listens to the AssociationChanged event and raises the appropriate PropertyChanged event.
public Book()
{
…
LendToReference.AssociationChanged += LendToReferenceAssociationChanged;
}
private void LendToReferenceAssociationChanged(object sender,
CollectionChangeEventArgs e)
{
// The navigation property LendTo doesn't support the PropertyChanged event.
// We have to raise it ourselves.
OnPropertyChanged("LendTo");
}
Looks like I've found a solution, though to me its more like a workaround. It's not the solution I
would have expected but it works.
The XAML is still the same as above, except for one thing. Instead of binding to File.Sender.Name, I bind to File.SenderName like this:
<Label Content="{Binding Path=File.SenderName}" Grid.Row="4"/>
SenderName in this case is a property of the object File which I added in a partial class like this:
public partial class File
{
public string SenderName
{
get
{
if (this.Sender != null)
{
return this.Sender.Name;
}
return string.Empty;
}
}
protected override void OnPropertyChanged(string property)
{
if (property == "SenderId")
{
OnPropertyChanged("SenderName");
}
base.OnPropertyChanged(property);
}
}
So what happens here is that if the SenderId property is changed, I tell the framework to also update the SenderName property. That's it. Works like a charm. Although I'm still not convinced that this is the way it is supposed to work.
Another workaround if you simply want a name is to overide ToString() for the Sender and bind directly to sender. This workaround is good because most of the time when we are databinding to Property of a Property we do it in order to get a "name" of object set as property value. Also this method works for Database First approach too if you edit tt files to add partial to all class definitions.
So you add a file to contain ToString extensions of your Entites and in it you add something like this:
public partial Contacts
{
public override string ToString()
{
return Name;
}
}
so you can databind
<Label Content="{Binding Path=File.Sender}" Grid.Row="5"/>
Now the databinding will detect if the Sender changes, and when it does it will call ToString to determine what to display.
On the other hand if you need to bind to another non standard property you might have problems. I do remember having success with using DataContext and templates to get around it. You bind to Sender and use DataTemplate to determine what to display.

Silverlight Control Binding

Is there a way to bind a Silverlight control to an object (or database table's row) which contains the values of several control's properties, doing so without by define the binding for each property?
For instance:
Let's say I have the class (or entity based on database table's row) with the following values:
class TextBlockValues
{
public string Text{get; set;}
public string HorizontalAlignment{get; set;}
public string VerticalAlignment{get; set;}
}
I want to bind it to a TextBlock in my silverlight application (again without explicit specify the binding for each property).
Thank you for your time.
There are two parts in a binding: DataContext and the actual Binding objects. Once you set up the data context for an item, all the properties, and children will automatically use that.
For example:
<TextBlock Name="CaptionText" Text="{Binding Text}" HorizontalAlignment="{Binding HorizontalAlignment}" Height="20" TextAlignment="Center" FontStretch="Expanded" FontSize="13" />
And in the .cs file:
CaptionText.DataContext = myObject;
If I understand your question right the answer is no. Even though you can set the control's DataContext you still have to bind which property in the control binds to what in the class.

How to do simple Binding in Silverlight?

I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.

Resources