I have a WPF view that displays a Shipment entity. I have a textblock that contains an asterisk which will alert a user that the record is changed but unsaved. I originally hoped to bind the visibility of this (with converter) to the Shipment.EntityState property.
If value = EntityState.Modified Then
Return Visibility.Visible
Else
Return Visibility.Collapsed
End If
The property gets updated just fine, but the view is ignorant of the change. What I need to know is, how can I get the UI to receive notification of the property change. If this cannot be done, is there a good way of writing my own IsDirty property that handles editing retractions (i.e. if I change the value of a property, then change it back to it's original it does not get counted as an edit, and state remains Unchanged).
Any help, as always, will be greatly appreciated.
Cory
After struggling with the same problem for a little bit, here is a solution that is working for me.
Lets say I have an entity called Trip that was generated by EF, I just needed to extend the class by means of partial class as showed below. The RaiseEntityStateChanged method is useful when you need to force a refresh of the EntytyState property, for example after calling the context's SaveChanges method.
partial class Trip
{
bool _forced = false;
System.Data.EntityState _lastState;
public Trip()
{
_lastState = EntityState;
this.PropertyChanged += (s, e) =>
{
if (_lastState != this.EntityState && e.PropertyName != "EntityState" || _forced)
{
_forced = false;
OnPropertyChanged("EntityState");
}
_lastState = this.EntityState;
};
}
public virtual void RaiseEntityStateChanged()
{
_forced = true;
OnPropertyChanged("EntityState");
}
}
I don't see a way to create a XAML binding on an existing property to do what you are trying to do. But you could write your own IsDirty property, based on the EntityState; you could update this value by subscribing to the PropertyChanged event raised by the base EntityObject. Of course, you'll need to also raise a PropertyChanged event for IsDirty (so that the GUI is notified) and ignore this event in your handler (to prevent infinite recursion).
Edit: added the following after question by OP:
This is how I see it, in order to answer the comment.
In the shipment class, one can add:
public bool IsDirty { get { return EntityState == EntityState.Modified; } }
public Shipment() {
...
PropertyChanged += OnShipmentChanged;
}
private void OnShipmentChanged(object sender, PropertyChangedEventArgs pcea) {
if (pcea.PropertyName != "IsDirty") { // prevent recursion
OnPropertyChanged("IsDirty"); // notifies binding listener that the state has changed
}
}
During the night, I thought of another way, which is to create a multi-binding on each Shipment property (which would replace this whole notion of an IsDirty property and would actually answer the original question). This could make sense if there are just a couple of Shipment properties. I'd say if there are more than 3, we should forget about this idea.
Related
How do I know when is a value of any control changed by user input (interaction with mouse or keyboard)?
Any suggestions? Is there a common way for this?
EDIT:
I am seeking for attached property or extending already given controls or whatever is needed just to get notified whether user input is taking place at the time value is changed. eg ComboBox SelectedItem changed by user input (User could enter text or select an item in drop down menu)
Its pure View thing therefore I am sorry but no ViewModel solutions for this issue will be accepted.
The simplest way would simply be to register to the Binding.TargetUpdated event, which is fired when the UI-side is updated (while Binding.SourceUpdated is fired when the ViewModel-side is updated)
Use a property with backing field in your view model. Introduce a second setter - a SetProp() method - that you use inside your ViewModel. That way you can add different behavior, depending on the origin of the call.
```
private bool mMyProp;
public bool MyProp
{
get { return mMyProp; }
set
{
if (mMyProp != value)
{
mMyProp = value;
// Todo: add here code specific for calls coming from the UI
RaisePropertyChanged(() => MyProp);
}
}
}
public void SetPropFromViewModel(bool value)
{
if (mMyProp != value)
{
mMyProp = value;
// Todo: add here code specific for calls coming from ViewModel
RaisePropertyChanged(() => MyProp);
}
}
I'm writing an application where I'm attempting to use an MVVM style architecture to handle my data binding (although I'm not using a MVVM specific library, such as MVVM Light). I've got a class which stores all of the information that my application requires, and then each of the screens is assigned a view model to its DataContext, which simply selects the values required for the specific screen, formatting the data if necessary.
As an example, the main data store looks something like this:
class DataStore {
int a, b, c;
string d;
DateTime e;
}
And then the view model allocated to a specific screen, which only uses several of the properties, is something like
class MainScreenViewModel {
public int data1 { get { return App.DataStore.a * App.DataStore.c } }
public int data2 { get { return App.DataStore.e.Day } }
}
This seems to work fine, when the page loads the data bindings are populated as they should be. However, they do not update automatically when the page loads. I've implemented INotifyPropertyChanged on the DataStore, but it seems that the change event doesn't bubble through to be reflected in the view model. I'm sure I'm going about this a really bad way, so if anyone could help point me in the right direction I'd be very grateful. I've read a stack of guides online, but I seem to be confusing myself more and more!
You have to implement INotifyPropertyChanged and raise PropertyChanged on your VM. In order to do this you will have to listen for DataStore.PropertyChanged. Sample:
class MainScreenViewModel {
public int data1 { get { return App.DataStore.a * App.DataStore.c } }
public int data2 { get { return App.DataStore.e.Day } }
public MainScreenViewModel()
{
App.DataStore.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "a" || e.PropertyName == "c")
RaisePropertyChanged("data1");
if (e.PropertyName == "e")
RaisePropertyChanged("data2");
};
}
private void RaisePropertyChanged(string propertyName)
{
// raise it
}
}
The only part not covered here is the scenario when e.Day will change in DataStore.
Your approach itself is not the bad and is definitely good enough to start with.
You're binding to the MainScreenViewModel class, so it is that class that needs to implement INotifyPropertyChanged for the UI to get updated when the underlying data gets updated.
You could either move the logic into MainScreenViewModel and raise property change notification there, or handle the PropertyChanged event on DataStore in MainScreenViewModel and raise property changed notification for the appropriate properties.
I would like to use the DataGrid.CanUserAddRows = true feature. Unfortunately, it seems to work only with concrete classes which have a default constructor. My collection of business objects doesn't provide a default constructor.
I'm looking for a way to register a factory that knows how to create the objects for the DataGrid. I had a look at the DataGrid and the ListCollectionView but none of them seems to support my scenario.
The problem:
"I'm looking for a way to register a factory that knows how to create the objects for the DataGrid". (Because my collection of business objects doesn't provide a default constructor.)
The symptoms:
If we set DataGrid.CanUserAddRows = true and then bind a collection of items to the DataGrid where the item doesn't have a default constructor, then the DataGrid doesn't show a 'new item row'.
The causes:
When a collection of items is bound to any WPF ItemControl, WPF wraps the collection in either:
a BindingListCollectionView when the collection being bound is a BindingList<T>. BindingListCollectionView implements IEditableCollectionView but doesn't implement IEditableCollectionViewAddNewItem.
a ListCollectionView when the collection being bound is any other collection. ListCollectionView implements IEditableCollectionViewAddNewItem (and hence IEditableCollectionView).
For option 2) the DataGrid delegates creation of new items to the ListCollectionView. ListCollectionView internally tests for the existence of a default constructor and disables AddNew if one doesn't exist. Here's the relevant code from ListCollectionView using DotPeek.
public bool CanAddNewItem (method from IEditableCollectionView)
{
get
{
if (!this.IsEditingItem)
return !this.SourceList.IsFixedSize;
else
return false;
}
}
bool CanConstructItem
{
private get
{
if (!this._isItemConstructorValid)
this.EnsureItemConstructor();
return this._itemConstructor != (ConstructorInfo) null;
}
}
There doesn't seem to be an easy way to override this behaviour.
For option 1) the situation is a lot better. The DataGrid delegates creation of new items to the BindingListView, which in turn delegates to BindingList. BindingList<T> also checks for the existence of a default constructor, but fortunately BindingList<T> also allows the client to set the AllowNew property and attach an event handler for supplying a new item. See the solution later, but here's the relevant code in BindingList<T>
public bool AllowNew
{
get
{
if (this.userSetAllowNew || this.allowNew)
return this.allowNew;
else
return this.AddingNewHandled;
}
set
{
bool allowNew = this.AllowNew;
this.userSetAllowNew = true;
this.allowNew = value;
if (allowNew == value)
return;
this.FireListChanged(ListChangedType.Reset, -1);
}
}
Non-solutions:
Support by DataGrid (not available)
It would reasonable to expect the DataGrid to allow the client to attach a callback, through which the DataGrid would request a default new item, just like BindingList<T> above. This would give the client the first crack at creating a new item when one is required.
Unfortunately this isn't supported directly from the DataGrid, even in .NET 4.5.
.NET 4.5 does appear to have a new event 'AddingNewItem' that wasn't available previously, but this only lets you know a new item is being added.
Work arounds:
Business object created by a tool in the same assembly: use a partial class
This scenario seems very unlikely, but imagine that Entity Framework created its entity classes with no default constructor (not likely since they wouldn't be serializable), then we could simply create a partial class with a default constructor. Problem solved.
Business object is in another assembly, and isn't sealed: create a super-type of the business object.
Here we can inherit from the business object type and add a default constructor.
This initially seemed like a good idea, but on second thoughts this may require more work than is necessary because we need to copy data generated by the business layer into our super-type version of the business object.
We would need code like
class MyBusinessObject : BusinessObject
{
public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
public MyBusinessObject(){}
}
And then some LINQ to project between lists of these objects.
Business object is in another assembly, and is sealed (or not): encapsulate the business object.
This is much easier
class MyBusinessObject
{
public BusinessObject{ get; private set; }
public MyBusinessObject(BusinessObject bo){ BusinessObject = bo; }
public MyBusinessObject(){}
}
Now all we need to do is use some LINQ to project between lists of these objects, and then bind to MyBusinessObject.BusinessObject in the DataGrid. No messy wrapping of properties or copying of values required.
The solution: (hurray found one)
Use BindingList<T>
If we wrap our collection of business objects in a BindingList<BusinessObject> and then bind the DataGrid to this, with a few lines of code our problem is solved and the DataGrid will appropriately show a new item row.
public void BindData()
{
var list = new BindingList<BusinessObject>( GetBusinessObjects() );
list.AllowNew = true;
list.AddingNew += (sender, e) =>
{e.NewObject = new BusinessObject(... some default params ...);};
}
Other solutions
implement IEditableCollectionViewAddNewItem on top of an existing collection type. Probably a lot of work.
inherit from ListCollectionView and override functionality. I was partially successful trying this, probably can be done with more effort.
I've found another solution to this problem. In my case, my objects need to be initialized using a factory, and there isn't really any way to get around that.
I couldn't use BindingList<T> because my collection must support grouping, sorting, and filtering, which BindingList<T> does not support.
I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also allows lets you choose which item is being added. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.
Even if you provide a handler for the event, DataGrid will refuse to allow the user to add rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.
If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception
Decompiling the control lets us see what's happening in there.
private object AddNewItem()
{
this.UpdateNewItemPlaceholder(true);
object newItem1 = (object) null;
IEditableCollectionViewAddNewItem collectionViewAddNewItem = (IEditableCollectionViewAddNewItem) this.Items;
if (collectionViewAddNewItem.CanAddNewItem)
{
AddingNewItemEventArgs e = new AddingNewItemEventArgs();
this.OnAddingNewItem(e);
newItem1 = e.NewItem;
}
object newItem2 = newItem1 != null ? collectionViewAddNewItem.AddNewItem(newItem1) : this.EditableItems.AddNew();
if (newItem2 != null)
this.OnInitializingNewItem(new InitializingNewItemEventArgs(newItem2));
CommandManager.InvalidateRequerySuggested();
return newItem2;
}
As we can see, in version 4.5, the DataGrid does indeed make use of AddNewItem. The contents of CollectionListView.CanAddNewItem are simply:
public bool CanAddNewItem
{
get
{
if (!this.IsEditingItem)
return !this.SourceList.IsFixedSize;
else
return false;
}
}
So this doesn't explain why we we still need to have a constructor (even if it is a dummy) in order for the add row option to appear. I believe the answer lies in some code that determines the visibility of the NewItemPlaceholder row using CanAddNew rather than CanAddNewItem. This might be considered some sort of bug.
I had a look at IEditableCollectionViewAddNewItem and it seems to be adding this functionality.
From MSDN
The IEditableCollectionViewAddNewItem
interface enables application
developers to specify what type of
object to add to a collection. This
interface extends
IEditableCollectionView, so you can
add, edit, and remove items in a
collection.
IEditableCollectionViewAddNewItem adds
the AddNewItem method, which takes an
object that is added to the
collection. This method is useful when
the collection and objects that you
want to add have one or more of the
following characteristics:
The objects in the CollectionView are different types.
The objects do not have a default constructor.
The object already exists.
You want to add a null object to the collection.
Although at Bea Stollnitz blog, you can read the following
The limitation of not being able to add a new item when the source has no
default constructor is very well
understood by the team. WPF 4.0 Beta 2
has a new feature that brings us a
step closer to having a solution: the
introduction of
IEditableCollectionViewAddNewItem
containing the AddNewItem method. You
can read the MSDN documentation about
this feature. The sample in MSDN shows
how to use it when creating your own
custom UI to add a new item (using a
ListBox to display the data and a
dialog box to enter the new item).
From what I can tell, DataGrid doesn’t
yet use this method though (although
it’s a bit hard to be 100% sure
because Reflector doesn’t decompile
4.0 Beta 2 bits).
That answer is from 2009 so maybe it's usable for the DataGrid now
The simplest way I could suggest to provide wrapper for your class without default constructor, in which constructor for source class will be called.
For example you have this class without default constructor:
/// <summary>
/// Complicate class without default constructor.
/// </summary>
public class ComplicateClass
{
public ComplicateClass(string name, string surname)
{
Name = name;
Surname = surname;
}
public string Name { get; set; }
public string Surname { get; set; }
}
Write a wrapper for it:
/// <summary>
/// Wrapper for complicated class.
/// </summary>
public class ComplicateClassWraper
{
public ComplicateClassWraper()
{
_item = new ComplicateClass("def_name", "def_surname");
}
public ComplicateClassWraper(ComplicateClass item)
{
_item = item;
}
public ComplicateClass GetItem() { return _item; }
public string Name
{
get { return _item.Name; }
set { _item.Name = value; }
}
public string Surname
{
get { return _item.Surname; }
set { _item.Surname = value; }
}
ComplicateClass _item;
}
Codebehind.
In your ViewModel you need to create wrapper collection for your source collection, which will handle item adding/removing in datagrid.
public MainWindow()
{
// Prepare collection with complicated objects.
_sourceCollection = new List<ComplicateClass>();
_sourceCollection.Add(new ComplicateClass("a1", "b1"));
_sourceCollection.Add(new ComplicateClass("a2", "b2"));
// Do wrapper collection.
WrappedSourceCollection = new ObservableCollection<ComplicateClassWraper>();
foreach (var item in _sourceCollection)
WrappedSourceCollection.Add(new ComplicateClassWraper(item));
// Each time new item was added to grid need add it to source collection.
// Same on delete.
WrappedSourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
InitializeComponent();
DataContext = this;
}
void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
foreach (ComplicateClassWraper wrapper in e.NewItems)
_sourceCollection.Add(wrapper.GetItem());
else if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (ComplicateClassWraper wrapper in e.OldItems)
_sourceCollection.Remove(wrapper.GetItem());
}
private List<ComplicateClass> _sourceCollection;
public ObservableCollection<ComplicateClassWraper> WrappedSourceCollection { get; set; }
}
And finally, XAML code:
<DataGrid CanUserAddRows="True" AutoGenerateColumns="False"
ItemsSource="{Binding Path=Items}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="SecondName" Binding="{Binding Path=Surname}"/>
</DataGrid.Columns>
</DataGrid>
I just wanted to provide an alternate solution to using a BindingList. In my situtation, the Business objects was held in an IEntitySet in a portable project (Silverlight), which did not support IBindingList.
The solution, first and foremost, is to subclass the grid, and overwrite the coerce callback for CanUserAddRows to use IEditableCollectionViewAddNewItem:
public class DataGridEx : DataGrid
{
static DataGridEx()
{
CanUserAddRowsProperty.OverrideMetadata(typeof(DataGridEx), new FrameworkPropertyMetadata(true, null, CoerceCanUserAddRows));
}
private static object CoerceCanUserAddRows(DependencyObject sender, object newValue)
{
var dataGrid = (DataGrid)sender;
var canAddValue= (bool)newValue;
if (canAddValue)
{
if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
{
return false;
}
if (dataGrid.Items is IEditableCollectionViewAddNewItem v && v.CanAddNewItem == false)
{
// The view does not support inserting new items
return false;
}
}
return canAddValue;
}
}
And then use the AddingNewItem event to create the item:
dataGrid.AddingNewItem += (sender, args) => args.NewItem = new BusinessObject(args);
And if you care for the details, here is the reason why it is a problem in the first place. The coerce callback in the framework looks like this:
private static bool OnCoerceCanUserAddOrDeleteRows(DataGrid dataGrid, bool baseValue, bool canUserAddRowsProperty)
{
// Only when the base value is true do we need to validate that the user
// can actually add or delete rows.
if (baseValue)
{
if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
{
// Read-only/disabled DataGrids cannot be modified.
return false;
}
else
{
if ((canUserAddRowsProperty && !dataGrid.EditableItems.CanAddNew) ||
(!canUserAddRowsProperty && !dataGrid.EditableItems.CanRemove))
{
// The collection view does not allow the add or delete action
return false;
}
}
}
return baseValue;
}
You see how it gets the IEditableCollectionView.CanAddNew? That means that it only enables adding when the view can insert and construct an item. The funny thing is that when we want to add a new item, it checks the IEditableCollectionViewAddNewItem.CanAddNewItem instead, which only asks if the view supports inserting new items (not creating):
object newItem = null;
IEditableCollectionViewAddNewItem ani = (IEditableCollectionViewAddNewItem)Items;
if (ani.CanAddNewItem)
{
AddingNewItemEventArgs e = new AddingNewItemEventArgs();
OnAddingNewItem(e);
newItem = e.NewItem;
}
In MVVM it is normal to connect View to the ViewModel with data binding.
Therefore if the name of a properties changes on one of the Model objects that is databound to there is no compiler error.
When the compiler will not stop a bug, the next thing I think of is “UnitTest”, However
How do you unit test this without
spending forever writing a GUI test?
Is there a system that will check that all the properties that are bound to is valid, (without having to run the UI) that I can call in a unit test?
I am looking for something that will take the view, and then loop over all WPF controls, for each WPF control it will look at all the binding and check if they are valid.
By the way there have been a few good questions about how to make OnPropertyChanged safe, and/or how to test it (But done of these get down to the level of a WPF view.)
How to make Databinding type safe and support refactoring
Automatically INotifyPropertyChanged
workarounds for nameof() operator in C#: typesafe databinding
A Fluent Interface For Testing INotifyPropertyChanged
Automatic Class Tester will test all simple proptites and INotifyPropertyChanged
I have put a bounty on this question, as someone must have thought hard about the problem and come up with soltions.
I think I've come up with something that may work using simple reflection, and adapting some code I've used in the past (the code for the FindBindingsRecursively method was written by Martin Bennedik's as part of his Enterprise WPF Validation Control):
[TestMethod]
public void CheckWpfBindingsAreValid()
{
// instansiate the xaml view and set DataContext
var yourView = new YourView();
yourView.DataContext = YourViewModel;
FindBindingsRecursively(yourView,
delegate(FrameworkElement element, Binding binding, DependencyProperty dp)
{
var type = yourView.DataContext.GetType();
// check that each part of binding valid via reflection
foreach (string prop in binding.Path.Path.Split('.'))
{
PropertyInfo info = type.GetProperty(prop);
Assert.IsNotNull(info);
type = info.PropertyType;
}
});
}
private delegate void FoundBindingCallbackDelegate(FrameworkElement element, Binding binding, DependencyProperty dp);
private void FindBindingsRecursively(DependencyObject element, FoundBindingCallbackDelegate callbackDelegate)
{
// See if we should display the errors on this element
MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Static |
BindingFlags.Public |
BindingFlags.FlattenHierarchy);
foreach (MemberInfo member in members)
{
DependencyProperty dp = null;
// Check to see if the field or property we were given is a dependency property
if (member.MemberType == MemberTypes.Field)
{
FieldInfo field = (FieldInfo)member;
if (typeof(DependencyProperty).IsAssignableFrom(field.FieldType))
{
dp = (DependencyProperty)field.GetValue(element);
}
}
else if (member.MemberType == MemberTypes.Property)
{
PropertyInfo prop = (PropertyInfo)member;
if (typeof(DependencyProperty).IsAssignableFrom(prop.PropertyType))
{
dp = (DependencyProperty)prop.GetValue(element, null);
}
}
if (dp != null)
{
// Awesome, we have a dependency property. does it have a binding? If yes, is it bound to the property we're interested in?
Binding bb = BindingOperations.GetBinding(element, dp);
if (bb != null)
{
// This element has a DependencyProperty that we know of that is bound to the property we're interested in.
// Now we just tell the callback and the caller will handle it.
if (element is FrameworkElement)
{
callbackDelegate((FrameworkElement)element, bb, dp);
}
}
}
}
// Now, recurse through any child elements
if (element is FrameworkElement || element is FrameworkContentElement)
{
foreach (object childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
{
FindBindingsRecursively((DependencyObject)childElement, callbackDelegate);
}
}
}
}
Really good question. Voted it up. I would like to know the answer too.
One of the best practices I know (suggested by Josh Smith, thanks Gishu for pointing to this) is having base view model class to check in the OnPropertyChanged() method whether property really exists. E.g.:
abstract class ViewModelBase
{
[Conditional("DEBUG")]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
if (this.ThrowOnInvalidPropertyName)
throw new ArgumentException(propertyName);
string msg = "Invalid property name: " + propertyName;
Debug.Fail(msg);
}
}
protected void OnPropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
But this wouldn't help you to find spelling problems in XAML. Hmm... I don't know any existing solution for this. Maybe guys from WPF Disciples could suggest something. I think I would go with PresentationTraceSources.DataBindingSource and add to his Listners collection instance of TextWriterTraceListener and then monitor output. As soon as we get an error or warning on our radar we should fail a test.
Found good example: WPF Snippet - Detecting Binding Errors
Hope this helps. At least a bit :).
Cheers, Anvaka.
I know that this is not the direct answer to your question.
If you know the name of the control element, that you expect to be bound against you can do something like the test below (using nunit). This is the rough version. But here you use expressions and explicitly test that the property is in a binding
[Test]
public void TestBindings()
{
TestBinding<IndividualSolutionViewModel, string>(x => x.Name, "Name", TextBlock.TextProperty);
}
private void TestBinding<TViewModel,TResult>(Expression<Func<TViewModel, TResult>> property, string elementName,
DependencyProperty dependencyProperty)
{
string memberName = ExpressionHelper.GetPropertyName(property); // f.ex v => v.Name will return Name
TestBinding(memberName, elementName, dependencyProperty);
}
private void TestBinding(string memberName, string elementInControlName, DependencyProperty dependencyProperty)
{
//object viewModel = new IndividualSolutionViewModel();
var view = new IndividualSolutionView();
//Assert.That(view.DataContext, Is.EqualTo(viewModel));
var element = view.FindName(elementInControlName);
Assert.That(element, Is.Not.Null, string.Format("Unable to find the element {0} in view {1}", elementInControlName, view.Name));
Assert.That(element, Is.InstanceOf(typeof(DependencyObject)));
var binding = BindingOperations.GetBinding(element as DependencyObject, dependencyProperty);
Assert.That(binding, Is.Not.Null, string.Format("Could not find a binding for the control {0}", elementInControlName));
Assert.That(binding.Path.Path, Is.EqualTo(memberName));
}
Ps. You have to add this to the app.config
<configSections>
<sectionGroup name="NUnit">
<section type="System.Configuration.NameValueSectionHandler"
name="TestRunner"></section>
</sectionGroup>
</configSections>
<NUnit>
<TestRunner>
<add value="STA" key="ApartmentState"></add>
</TestRunner>
</NUnit>
There is also this possibility, that might give you some ideas. THe gist of the idea is property names that you would be binding to are exposed as static string properties. If a binding property name changed you would get a compilation error.
I have not had the opportunity to actually test this technique myself - but it does look interesting:
http://www.codeproject.com/Articles/42036/Project-Metadata-Generation-using-T4
As Anvaka points out, using a base class for your view model that checks property names can help avoid this particular problem (though it won't tell you when your VM class does its own property-change notification and ignores the method in the base class, not that I've ever seen anything like that happen in my code).
And you can (and should) instrument your code so that things that aren't working fail in a way that's visible to you. The thing that's kind of paradoxical about this is that if you know what things may fail and you watch them, they won't, because the fact that you're watching them will keep you from making the mistakes that lead them to fail (like writing a template selector that doesn't always return a template).
But fundamentally, the view is the UI, so I would be pretty surprised to find methods of testing it that weren't also methods for testing the UI.
I have a ComboBox that has the SelectedItem bound to the ViewModel.
<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">
When the user selects a new Item in the View ComboBox, I want to display a prompt and verify that they want to make the change.
In the SetItem Property setter in the View Model, I display a Dialog to confirm the selection. When they say yes, it works fine.
My problem is, when the user clicks on "No" I am not sure who to get the ComboBox
to revert back to the previous value. The Property in the ViewModel has the correct
older value, however in the View the ComboBox displays the newly Selected Value.
I want the user to select an item, confirm they want to go ahead with it, and if they
decide not to, I want the ComboBox to revert back to the previous item.
How can I accomplish this?
Thanks!
When the user says "no", WPF is unaware that the value has changed. As far as WPF is concerned, the value is whatever the user selected.
You might try raising a property changed notification:
public object SelItem
{
get { ... }
set
{
if (!CancelChange())
{
this.selItem = value;
}
OnPropertyChanged("SelItem");
}
}
The problem is, the change notification happens within the same context of the selection event. Thus, WPF ignores it because it already knows the property has changed - to the item the user selected!
What you need to do is raise the notification event in a separate message:
public object SelItem
{
get { ... }
set
{
if (CancelChange())
{
Dispatcher.BeginInvoke((ThreadStart)delegate
{
OnPropertyChanged("SelItem");
});
return;
}
this.selItem = value;
OnPropertyChanged("SelItem");
}
}
WPF will then process this message after it's done processing the selection changed event and will therefore revert the value in the view back to what it should be.
Your VM will obviously need access to the current Dispatcher. See my blog post on a base VM class if you need some pointers on how to do this.
Thanks for this question and answers. The Dispatcher.BeginInvoke helped me and was part of my final solution, but the above solution didn't quite work in my WPF 4 app.
I put together a small sample to figure out why. I had to add code that actually changed the underlying member variable's value temporarily so that when WPF re-queried the getter, it would see that the value chaned. Otherwise, the UI didn't properly reflect the cancellation and the BeginInvoke() call did not do anything.
Here's a my blog post with my sample showing a non-working and a working implementation.
My setter ended up looking like this:
private Person _CurrentPersonCancellable;
public Person CurrentPersonCancellable
{
get
{
Debug.WriteLine("Getting CurrentPersonCancellable.");
return _CurrentPersonCancellable;
}
set
{
// Store the current value so that we can
// change it back if needed.
var origValue = _CurrentPersonCancellable;
// If the value hasn't changed, don't do anything.
if (value == _CurrentPersonCancellable)
return;
// Note that we actually change the value for now.
// This is necessary because WPF seems to query the
// value after the change. The combo box
// likes to know that the value did change.
_CurrentPersonCancellable = value;
if (
MessageBox.Show(
"Allow change of selected item?",
"Continue",
MessageBoxButton.YesNo
) != MessageBoxResult.Yes
)
{
Debug.WriteLine("Selection Cancelled.");
// change the value back, but do so after the
// UI has finished it's current context operation.
Application.Current.Dispatcher.BeginInvoke(
new Action(() =>
{
Debug.WriteLine(
"Dispatcher BeginInvoke " +
"Setting CurrentPersonCancellable."
);
// Do this against the underlying value so
// that we don't invoke the cancellation question again.
_CurrentPersonCancellable = origValue;
OnPropertyChanged("CurrentPersonCancellable");
}),
DispatcherPriority.ContextIdle,
null
);
// Exit early.
return;
}
// Normal path. Selection applied.
// Raise PropertyChanged on the field.
Debug.WriteLine("Selection applied.");
OnPropertyChanged("CurrentPersonCancellable");
}
}
Another way to do it (make sure you also read the comments):
http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html
From the link:
Another solution for issue of recursive calling of event handler without global variable is to cancel handler assignment before programmatic selection change, and reassign it after that.
Ex:
cmb.SelectionChanged -= ComboBox_SelectionChanged;
cmb.SelectedValue = oldSel.Key;
cmb.SelectionChanged += ComboBox_SelectionChanged;
My way of doing it is to let the change go through and perform validation in a lambda that is BeginInvoked in the Dispatcher.
public ObservableCollection<string> Items { get; set; }
private string _selectedItem;
private string _oldSelectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set {
_oldSelectedItem = _selectedItem;
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
Dispatcher.BeginInvoke(new Action(Validate));
}
}
private void Validate()
{
if (SelectedItem == "Item 5")
{
if (MessageBox.Show("Keep 5?", "Title", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
SelectedItem = _oldSelectedItem;
}
}
}
or in your ViewModel:
Synchronization.Current.Post(new SendOrPostCallback(Validate), null);