Validation against Linq Entity properties - wpf

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.

Related

WPF MVVM getting the textbox data to the ViewModel

I have read many questions on this and so far I've not been able to find the answer on this apparently simple issue.
I have a view model, in which is a property. In my XAML I have a TextBox with a binding to that property.
But the property never seems to change.
Here's the textbox:
<TextBox Grid.Row="1"
Grid.Column="0"
Margin="4"
Text="{Binding CharNameFromTB}" />
And the relevant code behind for the ViewModel:
private String _charNameFromTB;
String CharNameFromTB
{
get { return _charNameFromTB; }
set
{
if (!string.Equals(this._charNameFromTB, value))
{
this._charNameFromTB = value;
RaisePropertyChanged("CharNameFromTB");
}
}
}
I have put a break point on the if statement in the setter, but it never triggers. Have I missed something obvious out? I tried setting the binding mode to twoway but that didn't change anything.
It's driving me a little mad. Any help would be appreciated!
You should make the property public in order to be able to bind to it:
private String _charNameFromTB;
public String CharNameFromTB
{
get { return _charNameFromTB; }
set
{
this._charNameFromTB = value;
RaisePropertyChanged("CharNameFromTB");
}
}
Also make sure that you have set the DataContext of the TextBox or any of its parent elements to an instance of your view model class where the CharNameFromTB property is defined.
Also note that by default, the source property is set when the TextBox loses focus.
If you want to update the source property on each keystroke you should set the UpdateSourceTrigger property of the Binding to PropertyChanged:
<TextBox Grid.Row="1"
Grid.Column="0"
Margin="4"
Text="{Binding CharNameFromTB, UpdateSourceTrigger=PropertyChanged}" />

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

Binding a ListBox to an ObservableCollection of ViewModels

Is there a convention when using MVVM to bind the items of a ListBox to a ViewModel?
In the below XAML, I'm creating a ListBox of buttons. The ListBox is bound to an observable collection from my ViewModel. I then want to bind the button's Command property to an ICommand. The problem is that when I add that binding, I'm binding against the data object, not the ViewModel.
Do I just change the MyListOfDataObjects property to be a list of ViewModels? If so, where do I instantiate those new objects? I'd prefer to use dependency injection since they will have several dependencies. Do I change the GetData lambda?
In general: what's considered good practice here? I wasn't able to find any examples for this situation, although I assume it is rather common.
I'm using the MVVMLight framework, but I'm willing to look at any other frameworks.
<Window x:Class="KeyMaster.MainWindow"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate">
<Button Command="{Binding ButtonPressedCommand}"
CommandParameter="{Binding .}"
Content="{Binding Name}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ListBox ItemsSource="{Binding MyListOfDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</Window>
I'm using the standard MVVMLight ViewModel:
using GalaSoft.MvvmLight;
using KeyMaster.Model;
using System.Collections.ObjectModel;
namespace KeyMaster.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private ObservableCollection<MyData> _myListOfDataObjects;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
if (error != null)
{
return;
}
MyListOfDataObjects = new ObservableCollection<MyData>(item);
});
}
public ObservableCollection<MyData> MyListOfDataObjects
{
get { return _myListOfDataObjects; }
set
{
if (_myListOfDataObjects == value) return;
_myListOfDataObjects = value;
RaisePropertyChanged(() => MyListOfDataObjects);
}
}
}
}
Thanks.
In MVVM, there is a clear seperation between the raw data (also known as the Model) and the ViewModel. The ViewModel is the one who is in charge of parsing the data and even modifying it to whatever form it wishes, before passing it to the View.
A simple example is having the Model as XML and having the ViewModel parse it, take only a specific property (for example a "Name") from each element and add them to a list. Only this list will be shown in the View.
That said, I guess you can see where I'm going - the Command should be in the ViewModel not in the Model. As you stated by yourself, you should keep as much of the UI logic out of both the VM and the Model.
If you have a specific command that does something specific on a certain type of data, you can want it in a more "general" type of ViewModel, you can use the CanExectue to only allow this command in specific cases. But still, the command should sit in the ViewModel.
In your specific case, I don't see a problem having the command in the ViewModel, and when raised it will do whatever you need on your data. You don't need a list of ViewModels, you need only one.
I would say it'd depend where you want the functionality of the button-press. If it is always related to the MyData object then (if possible) would it be so out of place to put the Command in the MyData object? (ps. I wouldn't call your MyData object ViewModels just because you're adding a command property to them, as they're not associated with a view)
Alternatively if you want the command in the VM then you could try bind the command using the datacontext of the window. ie something like;
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ButtonPressedCommand}"
CommandParameter="{Binding .}"
Content="{Binding Name}" />
Although I've had trouble in the past with that and went with adding the command to the individual objects.

Validation between multiple fields in different levels

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.

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.

Resources