WPF - Refresh contents of a DataTemplate - wpf

I have a tab that has its content set to an object (a TFS WorkItem). I have a DataTemplate for the WorkItem type.
When I set the object to the tab it displays nicely.
However, when I update one of the collections on the object (the list of links) this change is not refreshed to the view.
I have tried making my WorkItem a DependencyProperty and I have also tried setting the value of the tab's content to null then to my object again (in the hopes that it will reload it).
None of this works.
Normally I would just use an observable collection to store the links in, but as I do not own the WorkItem class, I need a different solution that will manually refresh the DataTemplate.
Any ideas?

To force a binding to refresh the UI, call BindingExpression.UpdateTarget. To get the binding expression for a given element (in your case I assume an ItemsSource), use BindingOperations.GetBindingExpression. E.g.
BindingExpression bindingExpr = BindingOperations.GetBindingExpression(linksListBox, ListBox.ItemsSourceProperty);
bindingExpr.UpdateTarget(); // refreshes the ItemsSource
However, this relies on having a reference to the control whose property is bound, which may be difficult if the control is in a DataTemplate. You could try performing an UpdateTarget() on whichever control is hosting the DataTemplate (the Tab?) and whichever property is bound to the WorkItem (the Content property?) but I haven't tested this. (I'd be interested to know if it works!)

Related

WPF: How to trigger GUI behaviours in response to view-model events?

I'm developing a WPF/MVVM application and I have a listbox binding to data in a ViewModel. At various points I need the view model to cause the listbox to scroll to a given element.
How can I do this without creating a custom control and while still maintaining good separation of concerns?
I've currently got it working by creating a custom behavior class in the view layer with a dependency property VisibleIndex which the XAML code then binds to an integer in the view model:
<ListBox x:Name="myListBox"
local:ListBoxVisibilityBehavior.VisibleIndex="{Binding VisibleIndex}">
When the integer is set it triggers the dependency properties update handler which tells the listbox to scroll to the associated index.
This seems a bit hacky though because the dependency property value is never changed by the listbox and the update handler only gets called when the value changes, so the only way to ensure that the relevent item is visible is to do something like this:
// view-model code
this.VisibleIndex = -1;
this.VisibleIndex = 10;
The only reason I'm using a behaviour class at the moment is for binding my custom dependency property, is there a way to do something like this with events instead?
Attached properties are somewhat required in your case - as at some point, 'somewhere' you need to call the following method...
ListBox.ScrollIntoView(item)
or
ListBoxItem.BringIntoView();
And for that you need some sort of code behind - and attached properties/behaviors are a nice way of packaging that, w/o impacting your MVVM.
Having said that - if you just need to have your 'selected item' scrolled into view at all times (which is the case most of the time). Then you could use a different attached-property based solution (that again):
mvvm how to make a list view auto scroll to a new Item in a list view
All you have to do then is to set or bind to SelectedItem.
That's a bit 'nicer' if you wish - but the mechanism is the same.
For anyone else interested in the answer to this one of the MS engineers on the WPF forum cleared it up for me. Instead of binding to an event directly you bind to a wrapper object that encapsulates that event. The behaviour can then grab the reference to the wrapper from its DP and do whatever it wants with it i.e. subscribe to the event, trigger it etc.

How do I pass an extra variable to a a datatemplate

I have a DataTemplate(well two data templates) that I want to use as views for some
basic form viewmodels(that that contain a value and and boolean indicating whether I want to use the value).
I want to use the datatemplate(s) several times for separate form items. I think the right way to do this is to set it as the ContentControl's ContentTemplate (in that case it will have the same data context right?) but I also want to pass the label string and since the label string is part of the ui and doesn't change it seems wrong to put it in the viewmodel object. How do I give access of the label string to the DataTemplate instance?
Just like its name, a DataTemplate is used to template the Data... For example, if you have a class called MyItem which has a Name and Value and you want this shown in a specific way, you'll set a datatemplate for Item and use it whenever needed.
In your case, you're speaking about having very similar views, with only a minor change between them. This minor change (if I understood your question correctly) is not something that comes from the model or from the viewmodel but something which is entirely view-oriented (a different title for the page, for instance).
If you plan on using a different viewmodel for every view, and each viewmodel has a different purpose - I don't see a problem with adding a Title property to the VM and bind to that too (Remember, MVVM is a set of guidelines, not rules...)
If you still rather have it separated from the viewmodel, then you can use an Attached Property. Create an Attached Property called TemplateTitle, for instance, and have each contentcontrol in each view change it. The label, of course, will bind to that Attached Property.

At what stage in the life-cycle of Silverlight UIElements do existing Bindings get removed?

I'm trying to work out why some of my Silverlight controls are staying in memory. I've noticed that when I navigate away from a page with the controls on, there remain referential links to the view model. These links are a result of left-over subscriptions to the ErrorsChanged event (my view model implements INotifyDataError) created when Bindings are created between the Page and the view model. At some point some but not all of the Bindings are removed and I can't work out why this isn't happening.
At what point in the life-cycle of Silverlight UIElements do existing Bindings get removed?
I think I now know the answer to this. Please correct me if I'm wrong. Bindings are not removed. Instead referential links are made (e.g. INotifyDataError events are wired up) as a result of a particular instantiation of a Binding. In the simplest case that's when the DataContext changes.
Let's say you set the DataContext of a Page to a new, different INotifyDataError object as you load it. If there are Bindings on your Page (to the DataContext) the Page is not a candidate for garbage collection until the DataContext object is destroyed. That's because the DataContext holds a reference to the Page through its ErrorsChanged event. If you want the DataContext object to be collected you'll have to set the DataContext to null in the Page's Unloaded event.
As far as I understand it, it seems the proper pattern for implementing a DataContext (that is different from the Page) is setting the DataContext in the Loaded event on the page, and then setting the DataContext to null as the page fires its Unloaded event. This concept applies any Framework element.
As an aside, I haven't delved into element Bindings. For instance, when one element on a Page is bound to a property on another element. I'm not sure when the referential links between these objects are removed. I'm presuming it's when the page is unloaded. Anyone know the answer to this?

Silverlight Twoway Binding and rolling back changes - how to?

I have simple SL user control. A listbox which shows all customers and on the right a number of textboxes and comboboxes that are bound to the SelectedItem (Customer) in the listbox. The SelectedItem bound to SelectedCustomer property.
I am looking for a pattern/methodology to deal with canceling changes made to the customer (in the bound textboxes and combo boxes).
The edit controls (textboxes and combo's) can be one or two way bound to the selecteditem of the listbox.
If they are two way bound, immediate changes in the textboxes are reflected in the listbox. If they are oneway bound the changes in the textboxes are not reflected in the SelectedCustomer object.
At the bottom of the edit form i have typical Save, Cancel, Delete buttons. The save button for instance would take the SelectedCustomer object (if twoway bound and I would send through service for saving on server).
If the textboxes are one way bound i have to capture somehow the textbox values and insert into some object for sending to the server for saving.
If I use twoway binding , and say the save operation fails...i have to set the SelectedCustomer values back to original values otherwise the client now continues to see data that has not been saved.
There must be an easy way of dealing with this type of scenario....
RIA Services with Entity Framework already provides this functionality, basically how RIA services works and you can do it too as follow.
Each class implements interface called IEditableObject, which provides methods BeginEdit/EndEdit and CancelEdit. And it also stores (copies) instance of same class with name "OriginalEntity" with same values that were loaded from server.
After the form shows up for user to modify, BeginEdit is called, which probably caches every property using reflection, in some sort of dictionary. If you call CancelEdit, then values from OriginalEntity are loaded back in object.
Upon some errors while saving changes, you can either refresh the entities from server (best way) or you can try loading properties back from OringalEntity property.
I wouldn't discard user changes, as that easily leads to user frustration. IMHO, the user should not be informed about connection problems by uncontrolled data rollbacks.

.NET WinForm ListBox item displays old value untill reassigned to itself

I populate a ListBox control with my own objects redefining ToString(). The objects are displayed correctly when I just add those objects using listBox1.Add(myObject). However, if I later change something in this object, no changes are displayed in the listbox. Debugging reveals that an object inside listBox1.Items is indeed changed, but it is not reflected on a screen.
Interestingly enough, if I reassign a particular listbox item to itself (sounds a bit weird, doesn't it?), like:
listBox1.Items[0] = listBox1.Items[0]
this line will display a correct value on screen.
What is going on here? Does it have anything to do with threading?
Since you're using ToString of the object to provide the text of the list box item, the ListBox has no idea that the value has changed. What you should do instead is have the object implement INotifyPropertyChanged then expose a public property such as Name or Text and return what you normally would have returned from ToString().
Then set the DisplayMember of the ListBox to the name of the new property.
Make sure you are correctly raising the PropertyChanged event in the object and the ListBox should be able to automatically pick up the changes.
Edit: Adrian's edit reminded me that I do believe you'll need to use a BindingList as your data source in order for the property change notifications to be picked up. A quick scan in Reflector looks like ListBox on its own will not pick up the property changes mentioned above. But INotifyPropertyChanged + BindingList should.
The ToString() value of each item is cached when the listbox is first displayed. If an item in the listbox's Items collection then changes, the listbox does not notice and still uses the cached ToString() values for display. To force the listbox to update, either call RefreshItems() to refresh all items, or call RefreshItem(int) specifying the index of the item to refresh.
From the MSDN docs for RefreshItems():
Refreshes all ListBox items and retrieves new strings for them.
EDIT: It turns out that both of these methods are protected, so cannot be called externally. In trying to find a solution, I came across this SO question that this question is basically a duplicate of.
Have you tried calling Refresh() on the ListBox? I think the problem is that the ListBox does not know your object changed. The reason reassigning the the item works is because the ListBox will repaint itself when the collection changes.
you could invalidate the control, forcing a re-paint... perhaps..

Resources