why this wpf data binding fails - wpf

I'm a newer to study Wpf Data Binding feature, and recently I met one problem puzzled me a lot.
Assume we have some code like this in ViewModel:
private Person person;
public Person Person
{
get { return person; }
set
{
SetProperty<Person>(ref person, ref value, ()=>Person);
}
}
Ignore the terrible naming. Then I changed the Person property as following in Command's Execute() method:
//try 1
private void UpdateInfoExecute()
{
Person.Name="somebody";
Person.Age=22;
}
Finally, the data binding failed. As I change the implementation like this:
//try 2
Person = new Models.Person() { Age=22,Name="somebody"};
It succeed!
Well, in my opinion, try 1 also changed Person's property just like try 2 do. The PropertyChanged event listen on Person property, and sure it would be changed after try 1 did.
I don't know the theory of this, can anyone point me the key I missed?

I don't know what exactly SetProperty does, but in your "try1" you are changing Name and Age, not Person. So Name and Age properties are raised, the reference of Person was not changed and stayed the same. In your "try2" you actually set a new Person, therefore the property was changed and raised.
Solution: Make sure that Person also implements INotifyPropertyChanged and make sure that each property raises a PropertyChanged. Then everything should work fine.
Remember .Person is like .Name just a property.

Related

How should I pass property values from View to Model in MVVM?

I have three classes,
first:
public class Setting
which has properties:
string ID, string Value; (implements INotifyPropertyChanged on Value)
Second:
public class SettingCollection
which is a:
ObservableCollection<Setting>
Third:
public class SimObject
which has properties:
string ID, SettingsCollection Settings;
I have a View:
SettingsDisplay.xaml
which has dependency property:
SettingsCollection SimObjSettings;
and displays the setting's ID and with its Value in a TextBox inside an ItemsContainer.
My MainWindow ViewModel has a SimObject Jeff; and in the View I have
<local:SettingsDisplay SimObjSettings="{Binding Jeff.Settings}"/>
This all works fine.
I need to know when a Setting Value changes and when that happens I need to know the SimObject ID the Setting belongs to so that I can call a method that sends an event with the Setting ID, Value and the SimObject ID. (e.g. SendEvent(settingID, settingValue, targetObject))
Obviously Setting's and SettingCollection's have no knowledge of what SimObject they belong to.
The way I've tried to do this is in the Setting class call SendEvent in the Value property's Setter.
I'm struggling to find a suitable way to pass the SimObject's ID down the chain to the Setting's class, I also don't think this is a good solution.
What is the best way to acheive what I'm trying to do?
There are lots of ways to do this, but obviously all of them involve someone observing the SettingsCollection and dynamically attaching/detaching PropertyChanged handlers to all items inside it (at least that's what your requirements sound to me).
I would argue that if all you want is unconditional live updetes you should not involve the Views and ViewModels at all in this. Simply write an implementation of something like this:
interface ISimObjLiveUpdateService
{
void StartObserving(SimObject o);
bool IsObserving(SimObject o);
bool StopObserving(SimObject o);
}
The actual implementation would hook up to observe o.SettingsCollection and all items in it. Before you display a SimObject you StartObserving it, and when the view is closed you StopObserving it.
I ended up solving this by giving all Setting's a SimObject property, which was the setting's SimObject owner, and creating an event handler delegate void SettingHandler(string settingID, string settingValue, string targetObj);
in SimObject : public event SettingHandler SettingChanged;
public void RaiseSettingChangedEvent(string settingId, string settingValue, string targetObj)
{
if (SettingChanged != null)
{
SettingChanged(settingId, settingValue, targetObj);
}
}
In Setting on the string Value Setter:
set
{
_value = value;
RaisePropertyChanged("Value");
SimObject.RaiseSettingChangedEvent(ID, Value, SimObject.Settings["UID"].Value);
}
Then in SettingsDisplay I created a SelectedPropertyChangedCallback which adds the ParameterChanged event to the SimObject and also contains the SettingChanged method:
public void SettingChanged(string settingID, string settingValue, string targetObj)
{
Framework.GetBusinessDelegate().SendEvent(settingID, settingValue, targetObj);
}
Don't know how to add syntax highlighting to make this answer clearer.

A method set as ICollectionView.Filter sees other properties in the class as null even though they are not null

I'm trying to implement a basic filtered list box in WPF. The user types something and the list is narrowed to the values beginning with the typed phrase.
I have:
a View with:
a TextBox whose Text property is bound to InstitutionFilteringString property in the ViewModel class, which is set as the data context,
a ListBox whose ItemSource property is bound to an ICollectionView named Institutions in the View Model
a ViewModel class with the properties mentioned above.
Code (with irrelevant parts cut out):
class ChooseInstitiutionAndPublisherPageViewModel : WizardPageViewModelBase
{
private ICollectionView _institutions;
public ICollectionView Institutions
{
get
{
return _institutions;
}
set
{
_institutions = value;
NotifyPropertyChanged("Institutions");
}
}
private string _institutionFilteringString;
public string InstitutionFilteringString
{
get
{
return _institutionFilteringString;
}
set
{
_institutionFilteringString = value;
NotifyPropertyChanged("InstitutionFilteringString");
//WORKAROUND
//Institutions.Filter = new Predicate<object>(FilterInstitutions);
Institutions.Refresh();
}
}
public ChooseInstitiutionAndPublisherPageViewModel(WizardViewModel parent)
: base(parent)
{
Institutions = CollectionViewSource.GetDefaultView(CentralRepository.Instance.GetInstitutions());
Institutions.Filter = new Predicate<object>(FilterInstitutions);
}
private bool FilterInstitutions(object obj)
{
//I may refer directly to the field or through the property, it doesn't change anything
if (_institutionFilteringString == null || _institutionFilteringString.Length == 0)
return true;
//some more filtering, irrelevant
//[cut]
}
}
The view and the binding:
<TextBox Text="{Binding Path=InstitutionFilteringString, Mode=TwoWay}" Height="23" Margin="6,6,87,0" Name="institutionNameTextBox" VerticalAlignment="Top" TextChanged="institutionNameTextBox_TextChanged" />
<ListBox Margin="6,35" Name="institutionsListBox" ItemsSource="{Binding Path=Institutions}" IsSynchronizedWithCurrentItem="True" />
So, to the point. The setter for the InstitutionFilteringString is called correctly. Following an advice from here, the setter calls a Refresh() method on the collection view. The FilterInstitutions() method is called.
And now the bug: even though the string was set just before a second, inside the FilterInstitutions method it's null. If I go with the debugger down the call stack, from the point of view of the setter it's still set to the typed value, but inside the filtering method it's null.
In the setter there is a commented-out line of code. Uncommenting it fixes the bug, but it's hardly how it should be done.
What am I doing wrong?
(I'm not sure, but it seems to me as if the setter and the filtering method operated on two different instances of the class. But how is it possible, I create just one instance and the class is not clonable)
EDIT
I'm sorry, it seems I've lied. I've put a breakpoint in the constructor and it seems I indeed create two instances of the class and CollectionViewSource.GetDefaultView returns the same instance of ICollectionView for both. Well, but I want actually to have two views for the same collection. Well, I've followed this answer and it seems to work :)
do you create your Institutions once? and set the
Institutions.Filter = new Predicate<object>(FilterInstitutions)
once? if yes its ok :) can you post your code for this and also the code for FilterInstitutions methode? i do it all the way in my projects and have no problems.

WPF excessive PropertyChanged events

Typically in the property setter of an object we may want to raise a PropertyChanged event such as,
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public string UserNote
{
get { return _userNote; }
set
{
_userNote = value;
Notify("UserNote");
}
}
In our existing code base I see instances where PropertyChangedEventArgs is being sent null in order to indicate that all properties of the object have changed. This seems inefficient and seems to lead to far more events being triggered than is needed. It also seems to causes issues where objects update each other in a circular fashion.
Is this ever a good practice?
A comment in the code tries to justify it ...
//The purpose of this method is to wire up clients of NotificationBase that are also
//NotificationBases to *their* clients. Consider the following classes:
public class ClassA : NotificationBase
{
public int Foo
{
get { return 123; }
set { Notify("Foo"); }
}
}
public class ClassB : NotificationBase
{
ClassA A = new ClassA();
public ClassB()
{
A.PropertyChanged += AllChanged;
}
public void SetFoo()
{
A.Foo = 456;
}
}
public class ClassC
{
ClassB B = new ClassB();
public ClassC()
{
B.PropertyChanged += delegate { dosomething(); };
B.SetFoo(); // causes "dosomething" above to be called
}
}
/// ClassB.SetFoo calls ClassA.Foo's setter, which calls ClassA.Notify("Foo").
/// The event registration in ClassB's ctor causes ClassB.AllChanged to be called, which calls
/// ClassB.Notify(null) - implying that ALL of ClassB's properties have changed.
/// The event registration in ClassC's ctor causes the "dosomething" delegate to be called.
/// So a Notify in ClassA is routed to ClassC via ClassB's PropertyChanged event.
protected void AllChanged(Object sender, PropertyChangedEventArgs e)
{
Notify(null);
}
Any thoughts much appreciated.
Regards,
Fzzy
This is actually a problem with the design (or its documentation) of PropertyChangedEventArgs. Setting PropertyName to null means "all properties on this object have changed." But unless the class is sealed, or you're using reflection, you can't actually know that all properties on the object have changed. The most you can say is that all of the properties in the object's base class have changed.
This is reason enough to not use this particular convention, in my book, except in the vanishingly small number of cases where I create sealed classes that implement property-change notification.
As a practical matter, what you're really trying to do is just raise one event that tells listeners "a whole bunch of properties on this object have changed, but I'm not going to bother to tell you about them one by one." When you say:
I see instances where PropertyChangedEventArgs is being sent null in order to indicate that all properties of the object have changed. This seems inefficient and seems to lead to far more events being triggered than is needed.
...the actual intent is the exact opposite. If a method changes the Foo, Bar, Baz, and Bat properties on an object, and the object has only four or five properties, raising one event is probably better than raising four. On the other hand, if the object has sixty properties, raising four events is probably better making every one of the object's listeners - even those that aren't looking at those four properties - do whatever they do when the properties that they care about change, because those properties didn't.
The problem is that the property-change notification system, as designed, isn't a fine-grained enough tool for every single job. It's designed to be completely generic, and has no knowledge of a particular application domain built into it.
And that, it seems to me, is what's missing from your design: application domain knowledge.
In your second example, if a Fixture object has (say) ten properties that depend on the value of FixtureStatus, raising ten property-change events may seem a little excessive. Maybe it is. Maybe the object should raise a FixtureStatusChanged event instead. Then classes with knowledge of your application domain can listen to this one event and ignore the PropertyChanged event. (You still raise the PropertyChanged event on the other properties, so that objects that don't know what a FixtureStatusChanged event means can stay current - that is, if it's still necessary for your class to implement INotifyPropertyChanged once you've implemented FixtureStatusChanged.)
A secondary comment: Most classes in the C# universe, if they implement a method that raises the Foo event, call that method OnFoo. This is an important convention: it makes the relationship between the method and the event explicit, and it makes the fact that the code that's calling the method is raising an event easy to recognize. Notify is a weak name for a method in general - notify who? of what? - and in this case it actually obfuscates something that should be made explicit. Property-change notification is tricky enough without your naming convention concealing the fact that it's happening.
Ignoring the other stuff, I'd say the Notify(null) alone is a bad practice. It's not inherently clear what that means, and to a developer working the code 5 years down the line would probably assume that it meant something else unless they happened upon the comments.
I have come across situations wherein computed properties (without setters) need to fire PropertyChangeNotification when some other property i set via a setter.
eg
double Number
{
get { return num;}
set
{
num=value;
OnPropertyChanged("Number");
OnPropertyChanged("TwiceNumber");
}
}
double TwiceNumber
{
get {return _num * 2.0;}
}
As a rule I only do it with get only properties and I don't see why in this case a property firing a change notification on the other is bad. But I think if I do it for any other case I most likely don't know what I am doing!

WPF Binding: Refreshing Binding after reload of combos from database

I've got two combo's 'Make' and 'Model', they've got their SelectedValue properties bound to an Vehicle object with a ModelID and a MakeID.
Heres Model ...
<ComboBox DisplayMemberPath="Description" ItemsSource="{Binding Path=ModelSpecs}" SelectedValue="{Binding Path=Vehicle.ModelID}" SelectedValuePath="ID" />
A user can search for Vehicles in a seperate control and this swaps out the underlying Vehicle object. Everything works fine if your switching between vehicles of the same Make, however if the Make changes I go away to the database and reload the ModelSpec collection. The combo dosnt display the Model Description because the binding needs to be refreshed.
My current work-around is to add this at the end of the method thats reloading the Models - it works fine, but is not a particularly elegent solution.
var modelID = ViewModel.Vehicle.ModelID;
ViewModel.Vehicle.ModelID = string.Empty;
ViewModel.Vehicle.ModelID = modelID;
Basically I'm just triggering the INotifyPropertyChanged ...
private string _modelID;
public string ModelID
{
get { return _modelID; }
set
{
if (_modelID == value) return;
_modelID = value;
OnPropertyChanged("ModelID");
}
}
I can think of a couple of similar inelegant solutions - but there must be a better way?! Any help appreciated!
Just make ModelSpec collection observable (i.e. implement INotifyCollectionChanged yourself, or use ObservableCollection class for it).
Well, this is probably just another "inelegant" solution, but one more correct way would be to get the BindingExpression from the combo-box and call BindingExpression.UpdateSource.
Thanks for you assistance, in the end this did the trick and I prefer it to my first workaround.
It seems fine to me, but I guess others may gasp in horror? Please feel free to comment if so!
ModelSpecs is on my ManageVehicleViewModel so it dosnt seem that out of place to have the extra PropertyChanged call.
private IEnumerable<ModelSpec> _modelSpecs;
public IEnumerable<ModelSpec> ModelSpecs
{
get
{
return _modelSpecs;
}
set
{
if (_modelSpecs == value) return;
_modelSpecs = value;
OnPropertyChanged("ModelSpecs");
OnPropertyChanged("Vehicle");
}
}

What should Binding Path property be set to?

Assuming I have this struct definition in C#:
public struct TimeSlotInfo
{
public int TimeSlotID;
public int StartMin;
public int CalcGridColumn;
public string BackgroundCol;
public bool ToDisable;
}
And I have a linq query as so:
var TimeSlotsInfo =
from ts in datacon.TimeSlots
select new TimeSlotInfo
{
TimeSlotID = ts.TimeSlotID,
StartMin = ts.StartMin,
CalcGridColumn = CalcTimeSlotGridColumn(ts.StartMin),
BackgroundCol = ts.ColorName,
ToDisable = false
};
If i set the ItemsSource property of say a ListBox as below:
lstBox.ItemsSource = TimeSlotsInfo;
Now, how do i set a binding path to reference the "BackgroundCol" field from the above query result?
I've tried {Binding Path=TimeSlotInfo.BackgroundCol}, {Binding Path=TimeSlotInfo/BackgroundCol}, and finally {Binding Path=BackgroundCol}...none of them seem to be working..
Can anyone help? I've tried to simplify the example as possible. Hope my problem is clear enough.
Thanks in advance.
The last one is correct ({Binding Path=BackgroundCol}) - however, you can't bind to fields, you can only bind to Properties. Define your class to be:
class TimeslotInfo {
public int TimeslotId {get; set;}
/* Etc... */
}
Not only should you use properties for binding as Paul says, but in general you should avoid public fields in the first place.
In addition, this doesn't feel like it should be a struct - do you really want value-type semantics? When in doubt, you should default to creating classes in C# - it's pretty rare that you really want a struct.
Finally, even if you did want a struct, you should almost always make structs immutable. You're almost certain to have unexpected results from mutable structs. It's all well defined and for good reasons, but it's probably not the behaviour you expect. Mutable structs are evil.
If you want your items to display whatever is stored in BackgroundCol, you can just set the DisplayMemberPath property on your ListBox to "BackgroundCol". If this isn't what you're trying to achieve, please be more specific.
Hope this helps!!

Resources