Silverlight 3 data-binding child property doesn't update - silverlight

I have a Silverlight control that has my root ViewModel object as it's data source. The ViewModel exposes a list of Cards as well as a SelectedCard property which is bound to a drop-down list at the top of the view. I then have a form of sorts at the bottom that displays the properties of the SelectedCard. My XAML appears as (reduced for simplicity):
<StackPanel Orientation="Vertical">
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<TextBlock Text="{Binding Path=SelectedCard.Name}"
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=SelectedCard.PendingTransactions}"
/>
</StackPanel>
I would expect the TextBlock and ListBox to update whenever I select a new item in the ComboBox, but this is not the case. I'm sure it has to do with the fact that the TextBlock and ListBox are actually bound to properties of the SelectedCard so it is listening for property change notifications for the properties on that object. But, I would have thought that data-binding would be smart enough to recognize that the parent object in the binding expression had changed and update the entire binding.
It bears noting that the PendingTransactions property (bound to the ListBox) is lazy-loaded. So, the first time I select an item in the ComboBox, I do make the async call and load the list and the UI updates to display the information corresponding to the selected item. However, when I reselect an item, the UI doesn't change!
For example, if my original list contains three cards, I select the first card by default. Data-binding does attempt to access the PendingTransactions property on that Card object and updates the ListBox correctly. If I select the second card in the list, the same thing happens and I get the list of PendingTransactions for that card displayed. But, if I select the first card again, nothing changes in my UI! Setting a breakpoint, I am able to confirm that the SelectedCard property is being updated correctly.
How can I make this work???

If you are using Silverlight 3 you will need to use INotifyPropertyChanged.
Example:
public class CardViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> Cards { get; set; }
private Card _selectedCard;
public SelectedCard
{
get
{
return _selectedCard;
}
set
{
if (value != _selectedCard)
{
_selectedCard = value;
NotifyPropertyChanged("SelectedCard");
}
}
}
public CardViewModel()
{
Cards = new ObservableCollection<Card>();
//Populate Cards collection with objects
}
public void NotifyPropertyChanged(string item)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(item));
}
}
}
All you would need to do is set this class to your views DataContext and everything should be happy.

A pattern I've been using recently is to bind the data context of a container of detail info to the selected item of the list box. The XAML in your case becomes:
<StackPanel Orientation="Vertical">
<ComboBox x:Name="_lbxCards" <-- new
DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<StackPanel DataContext={Binding ElementName=_lbxCards,Path=SelectedItem}> <-- new
<TextBlock Text="{Binding Path=Name}" <-- updated
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=PendingTransactions}" <-- updated
/>
</StackPanel> <-- new
</StackPanel>

Turns out the problem isn't in the UI at all. The PendingTransactions class lazy-loads its values using a async WCF call to the server. The async pattern uses events to notify the caller that the operation is complete so the data can be parsed into the class. Because each Card has its own instance of the PendingTransactions class and we used a ServiceFactory to manage our WCF proxies, each instance was wiring up their event handler to the same event (we are using a singleton approach for performance reasons - for the time being). So, each instance received the event each time any of the instances triggered the async operation.
This means that the data-binding was working correctly. The PendingTransactions collections were overwriting themselves each time a new Card was viewed. So, it appeared that selecting a previous card did nothing when, in fact, it was selecting the correct object for binding, it was the data that was screwed up and make it look like nothing was changing.
Thanks for the advice and guidance nonetheless!

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?

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.

WPF - UI Not Updating When Item Added to Bound Collection

I have a Generic List of objects that I've bound to a custom control. Everything seems to work OK in the code-behind, but any changes I make to the collection don't seem to reflect in the UI (even though they're working find in all the code behind).
Here's the XAML for my UI:
<controls:ControllableListView x:Name="lvSummaryCaseEvents" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Label="Case Events:" ItemsSource="{Binding CaseEvents}" AddButtonClicked="ControllableListView_AddButtonClicked">
And the code behind where I add the item into the collection:
_caseScreen.CaseEvents.Add(caseEvent);
//after the line above executes, lvSummaryCaseEvents (in the debugger) shows the correct number of items and the items' values are all correct. No change to the UI whatsoever
The ItemSource property in my user control:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IList), typeof(ControllableListView));
public System.Collections.IList ItemsSource
{
get
{
return this.GetValue(ItemsSourceProperty) as System.Collections.IList;
}
set
{
this.SetValue(ItemsSourceProperty, value);
}
}
And finally, the XAML for a standard ListView that lives in my user control which is bound to the ItemsSource property listed above:
<ListView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Name="lvListView" ItemsSource="{Binding ItemsSource}" View="{Binding View}" SelectionChanged="lvListView_SelectionChanged" />
Just to reiterate, everything works fine when I display the collection the first time around, but when I add an item to the collection, the UI does not reflect the changes made.
Thanks in advance,
Sonny
Change your collection to ObservableCollection<T>

Diagnosing performance problems with databound WPF ComboBox

I've been battling a "slow" WPF ComboBox this morning, and would love to see if anyone has tips for debugging such a problem.
Let's say I have two ComboBoxes, A and B. When A changes, the items in B change as well. The ComboBoxes each have their SelectedItem and ItemsSource databound like this:
<ComboBox Grid.Column="1" ItemsSource="{Binding Names}" SelectedItem="{Binding CurrentName, Mode=TwoWay}" Margin="3" MinWidth="100" />
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding SubNames}" SelectedItem="{Binding CurrentSubName, Mode=TwoWay}" Margin="3" MinWidth="100" />
Whenever the list in B needs to change, I do this by clearing SubNames and then re-adding the entries based on the SelectedItem in A. This is done because overwriting SubNames with a new ObservableCollection<string> breaks the databinding.
Everything on one computer runs just as you'd expect. Select A, then click on B and the new items pop up immediately. On another computer, when I do this there is up to a 5 second pause before the ComboBox is rendered. The number of items is exactly the same. One difference is that on the slow machine, there is stuff going on in the background with hardware communication. I froze all of those threads and it didn't help.
My biggest problem is that I can't figure out where to even start looking. I need to see what the system is doing at the point that the ComboBox is clicked. I'm using databinding, so I can't put a breakpoint anywhere. I did try to change my declaration of SubNames from
public ObservableCollection<string> SubNames { get; set; }
to
private ObservableCollection<string> subnames_ = new ObservableCollection<string>();
public ObservableCollection<string> SubNames
{
get { return subnames_; }
set { subnames_ = value; }
}
and then put breakpoints in the getter and setter to see if there was excessive reading or writing going on, but there wasn't any.
Can anyone suggest a next step for me to try in determining the source of this slowdown? I don't believe it has anything to do with the ComboBox stock template, as described in this article.
While this may not directly answer your question, one suggestion would be to not bind directly to the ObservableCollection. Since the collection can raise a lot of events when manipulating its contents, it's better to bind the ItemsControl to an ICollectionView that represents that ObservableCollection, and when updating the collection use ICollectionView.DeferRefresh().
What I usually do is I make a class derived from ObservableCollection that exposes a DefaultView property, which lazily instantiates the ICollectionView corresponding to the collection. Then I bind all ItemsControls to the collection.DefaultView property. Then, when I need to refresh or otherwise manipulate the items in the collection, I use:
using (collection.DefaultView.DeferRefresh()) {
collection. // add/remove/replace/clear etc
}
This refreshes the bound controls only after the object returned by DeferRefresh() has been disposed.
Also be aware that the binding mechanisms in WPF have a default TraceSource you can use to glean more information on the bindings themselves; it doesn't trace the time, so I'm not sure how useful that is, but you can activate it with:
System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level = System.Diagnostics.SourceLevels.Verbose;
(or any other level you prefer).

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources