How do I update an IValueConverter on CollectionChanged? - wpf

Here's a basic example to explain my problem. Let's say I have
ObservableCollection<int> Numbers {get; set;}
and an IValueConverter that returns the sum of Numbers.
Normally what I'd do is changed the IValueConverter into an IMultiValueConverter and bind a second value to Numbers.Count like this
<MultiBinding Converter="{StaticResource SumTheIntegersConverter}">
<Binding Path="Numbers" />
<Binding Path="Numbers.Count" />
</MultiBinding>
However I'm unable to use this method to solve my actual problem. It seems like there should be a better way to update the binding when the collection changes that I'm just not thinking of. What's the best way to get the value converter to run when items are added and removed to Numbers?

This is actually surprisingly very difficult. An IValueConverter doesn't update, so this does not work as you'd hope.
I wrote a sample on the Microsoft Expression Gallery called Collection Aggregator that shows a working, if convoluted, approach to making this work via a Behavior that does the aggregation (Count, in your case, although I also support Sum, Average, etc) for you, instead of a converter.

In your model, subscribe to CollectionChanged and raise PropertyChanged:
Numbers.CollectionChanged += (o,e) =>
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Numbers)));
And, as thomasgalliker mentioned, you should unsubscribe from the event when the model containing the connection is no longer used.

I ended up doing something like this which seems to work. It's far from an optimal solution and I'd still be interested in something better but it seems to work for my purposes.
class CollectionChangedHandlingValueConverter : IValueConverter
{
DependencyObject myTarget;
DependencyProperty myTargetProperty;
//If this ever needs to be called from XAML you can make it a MarkupExtension and use ProvideValue to set up the Target and TargetProperty
public CollectionChangedHandlingValueConverter(DependencyObject target, DependencyProperty dp)
{
myTarget = target;
myTargetProperty = dp;
}
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
INotifyCollectionChanged collection = value as INotifyCollectionChanged;
if (collection != null)
{
//It notifies of collection changed, try again when it changes
collection.CollectionChanged += DataCollectionChanged;
}
//Do whatever conversions here
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
void DataCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if ((myTarget != null) && (myTargetProperty != null))
{
BindingOperations.GetBindingExpressionBase(myTarget, myTargetProperty).UpdateTarget();
}
}
}

And I ended up synchronizing collection (original with converter), take a look at the buttom of my post for example:
http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/

Related

Silverlight UI not updating - ObservableCollection being reinstantiated

I'm pretty new to ObservableCollections, but have built some code which I'm sure should work. Unfortunately it doesn't. The only thing that is not happening, is my GUI is not being updated. I know the values are being updated in the back (Checked using Debugger).
What am I doing wrong?
Here with a sample of my XAML for the Textblock:
<TextBlock Name="tbCallsOpen" Text="{Binding IndicatorValue}" />
Herewith sample of my code behind:
public partial class CurrentCalls : UserControl
{
Microsoft.SharePoint.Client.ListItemCollection spListItems;
ObservableCollection<CurrentCallIndicator> CallIndicators = new ObservableCollection<CurrentCallIndicator>();
public CurrentCalls()
{
InitializeComponent();
DispatcherTimer dispatchTimer = new DispatcherTimer();
dispatchTimer.Interval = new TimeSpan(0, 0, 20);
dispatchTimer.Tick += new EventHandler(BindData);
dispatchTimer.Start();
}
private void BindData(object sender, EventArgs args)
{
//splistitems is a sharepoint list. Data is being retrieved succesfully, no issues here.
foreach (var item in spListItems)
{
//My custom class which implements INotifyPropertyChanged
CurrentCallIndicator indicator = new CurrentCallIndicator();
indicator.IndicatorValue = item["MyValueColumn"];
//Adding to ObservableCollection
CallIndicators.Add(indicator);
}
//Setting Datacontext of a normal TextBlock
tbCallsOpen.DataContext = CallIndicators.First(z => z.IndicatorName == "somevalue");
}
}
You are most likely assuming that changes to the underlying items in the collection will raise the CollectionChanged event; however that is not how the ObservableCollection<T> works.
If you wanted this behavior you would need to roll your own implmentation and when a PropertyChanged event is fired within an item within your collection, you would then need to fire the CollectionChanged event.
Your code looks more-or-less correct to me, at first blush - though I wouldn't expect that you'd need to use an ObservableCollection<> to get the results you seem to be expecting: a simple List<> would work just fine.
If the debugger tells you that the DataContext is being updated correctly to the expected item, then the most likely issue is that there's a problem with how your binding is defined. If you're not seeing any binding errors reported in your debug window, then I'd look into Bea Stollnitz' article on debugging bindings. Most specifically, I often use the technique she suggests of a "DebugValueConverter", e.g.:
/// <summary>
/// Helps to debug bindings. Use like this: Content="{Binding PropertyName, Converter={StaticResource debugConverter}}"
/// </summary>
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
And then set a breakpoint in your converter, and watch what's happening. It's a hack and a kludge, but until we're all on SL5 (which has binding debugging built in), it's your best bet.
Ok, Sorted. I fixed the issue myself. Because I was updating the values in a loop, the ObservableCollection wasn't being updated properly. All I did in the beginning of the databinding method, was to Clear the collection : CallIndicators.Clear();

Reversed Listbox without sorting

I spent last two weeks trying to figure out a method to display the items of a listbox in reversed order without using any sort property and without putting any presentation logic in my entities, I just want that the last inserted item is displayed at the top of the listbox.
The only pure XAML solution I've found is this WPF reverse ListView but it isn't so elegant. I've also tried to override the GetEnumerator() of my BindableCollection (I use Caliburn Micro as MVVM framework) to return an enumerator that iterate over my collection's items in the reverse order but id did not work.
How can I do?
ScaleTransform is an elegant solution to this particular case, but there could be more generic applications (such as binding the same list but with different permutations applied).
It is possible to do this all with converters if you make sure to consider that the binding is to the list, rather than the elements of the list. Assuming that your using an ObservableCollection of strings (sure it would be possible to use generics and reflection to make this more elegant) and missing out all the proper coding of exception handling and multiple calls to Convert...
public class ReverseListConverter : MarkupExtension, IValueConverter
{
private ObservableCollection<string> _reversedList;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_reversedList = new ObservableCollection<string>();
var data = (ObservableCollection<string>) value;
for (var i = data.Count - 1; i >= 0; i--)
_reversedList.Add(data[i]);
data.CollectionChanged += DataCollectionChanged;
return _reversedList;
}
void DataCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var data = (ObservableCollection<string>)sender;
_reversedList.Clear();
for (var i = data.Count - 1; i >= 0; i--)
_reversedList.Add(data[i]);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You can then bind within your XAML with something like
<ListBox ItemsSource="{Binding Converter={ReverseListConverter}}"/>

How can a ViewModel request data from a view when it needs it?

I have a calculated property on my View that I need to bind to my ViewModel. I'm using WPF and it seems that there is no way to make a bindable property (Dependency Property) that is self calculating. I don't want to perform the calculations whenever the View's state changes because they are time intensive. I want to do the calculations whenever the ViewModel needs the result, i.e. when it closes.
Based on your comment above, I'd use a Converter
Your ViewModel would contain the encrypted data, and the binding to the View uses a Converter which converts it into something readable. When it's time to save the data back to the ViewModel, use the ConvertBack method of the converter to encrypt the data again.
<TextBox Text="{Binding EncryptedAccountNumber,
Converter={StaticResource DecryptTextConverter}}" />
public class DecryptTextConverter: IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Implement decryption code here
return decryptedValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Implement encryption code here
return ecryptedValue;
}
}
If the Encryption code takes a while, set your UpdateSourceTrigger=Explicit and manually trigger the source update when the Save button is clicked.
This is my solution. It works the same way as ICommand but the view provides the delegate (CalculationDelegate) and the view model calls CanExecute and Execute. Its not pure MVVM but it works.
public interface ICalculationProvider<TResult>
{
event EventHandler CanExecuteChanged;
Func<TResult> CalculationDelegate { get; set; }
bool CanExecute();
TResult Execute();
bool TryExecute(out TResult a_result);
}
I have marked Rachel's answer as correct, simply because what I am doing here is not pure MVVM.

WPF: Is there a way to use a ValueConverter without defining a resource?

Is there a way to use a ValueConverter without defining it in a resource? As is the syntax is pretty verbose.
You can use a MarkupExtension to minimise the amount of xaml code required. E.g:
public class MyConverter: MarkupExtension, IValueConverter
{
private static MyConverter _converter;
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
// convert and return something
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
// convert and return something (if needed)
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null)
_converter = new MyConverter();
return _converter;
}
}
You end up with a syntax like this:
{Binding Converter={conv:MyConverter}}
This approach has an added advantage of ensuring that all your converters are singletons.
This article does a great job of explaining the concept and provides sample code.
Within your converter you can have a static property or field that you can refer to in xaml. No need for adding a resource.
public class MyConverter : IValueConverter
{
public static readonly MyConverter Instance = new MyConverter();
... }
And in XAML
<TextBlock Text="{Binding Path=., Converter={x:Static l:MyConverter.Instance}}" />
Beaware that these converters should not store any state as the same instance will be used.
If you are iffy about public fields just create a static property instead.
Though it is debatable that this is better
You could create an attach property to hook up to the binding and perform the conversion, though if the only reason is for breverity, I wouldn't recommend adding an extra piece of complexity to your code.
How exactly are you declaring these converters such that verbosity is an issue?
<conv:NegatingConverter x:Key="NegatingConverter" />
One line per converter per application.
Usage isn't verbose either.
Converter="{StaticResource NegatingConverter}"

WPF - Dynamically access a specific item of a collection in XAML

I have a data source ('SampleAppearanceDefinitions'), which holds a single collection ('Definitions'). Each item in the collection has several properties, including Color, which is what I'm interested in here.
I want, in XAML, to display the Color of a particular item in the collection as text. I can do this just fine using this code below...
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[0].Color}"
The only problem is, this requires me to hard-code the index of the item in the Definitions collection (I've used 0 in the example above). What I want to do in fact is to get that value from a property in my current DataContext ('AppearanceID'). One might imagine the correct code to look like this....
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[{Binding AppearanceID}].Color}"
...but of course, this is wrong.
Can anyone tell me what the correct way to do this is? Is it possible in XAML only? It feels like it ought to be, but I can't work out or find how to do it.
Any help would be greatly appreciated!
Thanks!
AT
MultiBinding is your friend here:
Assuming you have a TextBlock:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AppearanceIDConverter}">
<Binding Source="{StaticResource SampleAppearanceDefinitions}" />
<Binding Path="AppearanceID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And define a MultiValueConverter to return what you wish to see:
public class AppearanceIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<item> items = (List<item>)values[0]; //Assuming its items in a List
int id = (int)values[1]; //Assuming AppearanceID is an integer
return items.First(i => i.ID == id).Color; //Select your item based on the appearanceID.. I used LINQ, but a foreach will work just fine as well
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
#endregion
}
Of course, you will need to set the converter as a resource in your Resource dictionary, like you did SampleAppearanceDefinitions. You can also ditch the multibinding and use a regular binding to AppearanceID with a IValueConverter, if you can get to the SampleAppearanceDefinitions collection through code ;).
Hope this helps
Even if it could be possible you'd better not do that this way, but instead use a dedicated property in your view model or in the code behind of your view if it has only a pure graphical meaning.
This property, say "CurrentAppearance", would expose a Color property you could bind from your Xaml :
Text="{Binding CurrentAppearance.Color}"
which is more understandable.
As a general advice : avoid to spoil your Xaml with plumbing code : Xaml should be as readable as possible,
particularly if you work with a team of designers that have no coding skills and do not want to be concerned with the way you are managing the data.
Moreover, if later you decide to change the way data are managed you would not have to change your Xaml.
MultiBinding might actually work if your list is on a viewmodel instead of a staticresource. I was suprised myself to see that the object passed on to the view is actually a pointer to the object on the model, so changing the object in the view (eg. typing in new test in the textbox) directly affects the model object.
This worked for me. The ConvertBack method is never useed.
public class PropertyIdToPropertyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
var properties = values[0] as ObservableCollection<PropertyModel>;
if (properties != null)
{
var id = (int)values[1];
return properties.Where(model => model.Id == id).FirstOrDefault();
}
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources