Silverlight Databinding - Binding a ValueConverter to a property on a view-model - silverlight

Let's pretend I have the following xaml...
<UserControl.Resources>
<local:ViewModel x:Name="viewModel" />
<local:LoadChildrenValueConverter x:Name="valueConverter" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource viewModel}" />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<control:TreeView ItemsSource="{Binding Root}">
<control:TreeView.ItemTemplate>
<control:HierarchicalDataTemplate ItemsSource="{Binding Converter={StaticResource valueConverter}}">
<TextBlock Text="{Binding}" />
</control:HierarchicalDataTemplate>
</control:TreeView.ItemTemplate>
</control:TreeView>
</Grid>
...and the following code to go with it...
using System;
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace SilverlightViewModelSpike
{
public class ViewModel
{
public ViewModel()
{
Root = new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
}
public ObservableCollection Root { get; private set; }
}
public class LoadChildrenValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
This works as expected, but it feels wrong that I have two separate classes that are required in order to grab the needed data for my view (imagine that ViewModel and LoadChildrenValueConverter pulled data from a web service instead of returning hard coded data). Is there a better solution here? I was thinking maybe something like this...
using System;
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace SilverlightViewModelSpike
{
public class ViewModel
{
public ViewModel()
{
Root = new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
ValueConverter = new LoadChildrenValueConverter();
}
public ObservableCollection Root { get; private set; }
public LoadChildrenValueConverter ValueConverter { get; private set; }
}
public class LoadChildrenValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
... but then i can't get this line to work...
<control:HierarchicalDataTemplate ItemsSource="{???}">
...and even that doesn't seem like a great solution. Does anyone have a nice clean solution for this?

Since you're using a ViewModel to sit between your actual model and your view, I wonder if it's easier just to implement the IValueConverter logic directly in there. Sort of like:
public class ViewModel
{
public ObservableCollection Root { get; set: }
public ObservableCollection Children
{
get { /* return children items */ }
}
}
Then you can simply bind directly to your second property:
<control:HierarchicalDataTemplate ItemsSource="{Binding Children}">
I think the main purpose of a ViewModel object is to limit the number of "tricks" (such as IValueConverters) you need to pull to get the data you need from the original model. Since you have one, you might as well make use of it.
Edit 1
... and of course now that I re-read your post I see that there's more to it. You're getting the children for each item in your "Root" collection.
How about implementing the IValueConverter as a static instance in your ViewModel itself?
public class ViewModel : IValueConverter
{
public static readonly IValueConverter ChildrenConverter
= new LoadChildrenValueConverter();
}
Now you should be able to say:
<control:HierarchicalDataTemplate
ItemsSource="{Binding Converter={x:Static local:ViewModel.ChildrenConverter}}">
Edit 2
Ok, you're using Silverlight so {x:Static} isn't available to you.
The only other option I can think of that will let you reuse the one static resource instead of having to declare two is to implement the IValueConverter directly in your ViewModel. This is no good if you will need to do more than one type of conversion, but if your ViewModel is very narrow-focused then it could do the job. So:
public class ViewModel : IValueConverter
{
// move your Convert and ConvertBack methods into here
}
Now you can do this:
<control:HierarchicalDataTemplate
ItemsSource="{Binding Converter={StaticResource ViewModel}}">

Sorry guys I am a little confused about what you are trying to do here... Anyway, from the title it sounds as if you want to a property in your value converter to a property in your value converter. Firstly have a look at an article I have written explaining exactly how you can do that:
http://nick-howard.blogspot.com/2011/06/silverlight-4-value-converter.html
So what you would do is create an ObvervableCollection dependency property in your LoadChildrenValueConverter, for arguments sake let’s call it Children.
Then in your xaml you can change the LoadChildrenValueConverter to something like this:
That way you are only calling the web service once from your view model, and you are then sharing that ObvervableCollection in your view model with your value converter.
Hope that helps.

Related

IValueConverter and binding a DependencyObject

I have a ComboBox that I need to do a converter on the SelectedItem. Problem is the IValueConverter needs the binding value but a Collection as well. Configured a DependencyObject but it is giving me an error message of
Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.ObservableCollection`1[MyClass]'.
Here is my IValueConverter
public class MyConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty FoldersProperty =
DependencyProperty.Register(nameof(MyCollection), typeof(ObservableCollection<MyClass>), typeof(MyClassModelToMyClassID), new FrameworkPropertyMetadata(new ObservableCollection<MyClass>()));
public ObservableCollection<MyClass> MyCollection
{
get { return GetValue(FoldersProperty) as ObservableCollection<MyClass>; }
set { SetValue(FoldersProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//Amazing Convert code that uses MyCollection and Value
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//Amazing ConvertBack code that uses MyCollection and Value
}
}
Here is how I am calling it:
<Page.Resources>
<converter:MyConverter x:Key="Converter" MyCollection="{Binding DataCollection}" />
</Page.Resources>
....
<ComboBox
ItemsSource="{Binding DataCollection}"
SelectedItem="{Binding Path=MyValue, Converter={StaticResource TaxCodeConverter}}" />
edit: added the full IValueConvert subtracted the Convert and ConvertBack code
Like a BindingProxy, it needs to be a Freezable. Also, don't pass a new observable collection to your metatadata. That's a reference type, so all instances of this converter will be initialized with the same actual collection instance.
Let me know if you run into some other issue, but I've done this and been able to bind to the dependency property.
Many would argue that a better approach would be a multibinding and a multi-value converter. I think there's value in having a strongly typed property with a descriptive name.
public class MyConverter : Freezable, IValueConverter
{
/* omitted: Convert() and ConvertBack() */
public MyConverter()
{
// Initialize here if you need to
MyCollection = new ObservableCollection<MyClass>();
}
protected override Freezable CreateInstanceCore()
{
return new MyConverter();
}
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.Register(nameof(MyCollection),
typeof(ObservableCollection<MyClass>), typeof(MyConverter),
new FrameworkPropertyMetadata(null));
public ObservableCollection<MyClass> MyCollection
{
get { return GetValue(MyCollectionProperty) as ObservableCollection<MyClass>; }
set { SetValue(MyCollectionProperty, value); }
}
}
XAML usage will be just as you have it in your question: Bind the dependency property, and the binding will update that property of that instance of MyConverter, provided that your Page's DataContext has an appropriately typed property named DataCollection.
<Page.Resources>
<converter:MyConverter x:Key="Converter" MyCollection="{Binding DataCollection}" />
</Page.Resources>

wpf IValueConverter not updating the view

I am trying to use IValueConverter to convert the collection into proxy object for data binding.
The converter seems to work fine, but the problem is when a new object is added or removed from the collection. The same is not refreshed in the view..
Model Object:
public class A {
public ObservableCollection<string> Members { get; }
}
Converter
public class MemberConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var collection = new CompositeCollection();
var a = value as A;
a.Members.ToList().ForEach(member => {
collection.Add(new ProxyClass{ A= a, Member= member });
});
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new System.NotImplementedException();
}
}
Proxy Class
public class ProxyClass {
public A A { get; set; }
public string Member{ get; set; }
}
XAML:
<DataTemplate DataType="{x:Type my:ProxyClass}">
<TextBlock Text="{Binding Path=Member}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Converter={StaticResource MemberConverter}}">
<TextBlock Text ="{Binding}"/>
</HierarchicalDataTemplate>
A Binding will only be re-evaluated if a property change notification for the property to which it is bound has been changed. In this case the ItemsSource is bound to the DataContext - the A instance itself - so unless it is given a new A instance it will not be re-evaluated. There is nothing listening to the change notifications that the collection raises since the value given to the ItemsSource is actually a different collection instance that you are creating within your converter.
One option would be to have the converter create a helper class that hooks the CollectionChanged event of the source collection (i.e. the value passed into the converter) and that object would be responsible for keeping the source collection and the one it creates in sync. Another option is to try to force the binding to get re-evaluated - e.g. use a Path of "Members" for the ItemsSource binding and when you change the contents of the collection raise a property change notification for "Members" on A.
Its not updating because your A Property doesn't implement INotifyPropertyChanged or is a DependencyProperty
If need be you can add the following after making it implement one of the previous.
ItemsSource="{Binding Converter={StaticResource MemberConverter}, UpdateSourceTrigger=PropertyChanged}">

How Do You Bind To An Item In A Dictionary?

I'm using the RadCarousel control from Telerik in a WPF application with C#. RadCarousel is similar to a GridView in that it binds to a collection and show's each item in the collection (so my question isn't specific to Telerik or RadCarousel).
I'm using a DataTemplate to specify how each record should get displayed.
If I do this it work's fine:
<DataTemplate>
<TextBlock Text="{Binding Path=oMySubObject.TheAmount}" />
</DataTemplate>
But what if I need to point to an item in a dictionary?
<DataTemplate>
<TextBlock Text="{Binding Path=myDictionaryOfSubObjects[TheCurrentIndex].TheAmount}" />
</DataTemplate>
This I can't get working and don't know how. Basically...I need the index to be specified at runtime and when it gets updated, the binding updates.
Any advice?
<Window.Resources>
<NAMESPACEWHERECONVERTERRESIDES:DictionaryConverter x:Key="cDictionaryConverter"/>
</WindowResources>
<TextBlock Text="{Binding Path=myDictionaryOfSubObjects, Converter={StaticResource cDictionaryConverter}}"/>
// Something like this:
[ValueConversion(typeof(Dictionary), typeof(string))]
public class DictionaryConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Dictionary<type, type> dict = value as Dictionary<type, type>;
return dict[CurrentIndex].TheAmount;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return 0;
}
}
You can only use constant values in the indexer, TheCurrentIndex will not be resolved. There are a few workarounds, like passing the dictionary and the index to a multi value converter to resolve the item there.
With anything complicated, you should probably make a getter that does the job, and bind to that.
What is "TheCurrentIndex"?
Window1.xaml
<Window x:Class="QuizBee.Host.Window1"
x:Name="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListView ItemsSource="{Binding ElementName=Window1, Path=myDictionary}" />
</Window>
Window1.xaml.cs
public partial class Window1:Window
{
// the property must be public, and it must have a getter & setter
public Dictionary<string, myClass> myDictionary { get; set; }
public Window1()
{
// define the dictionary items in the constructor
// do the defining BEFORE the InitializeComponent();
myDictionary = new Dictionary<string, myClass>()
{
{"item 1", new myClass(1)},
{"item 2", new myClass(2)},
{"item 3", new myClass(3)},
{"item 4", new myClass(4)},
{"item 5", new myClass(5)},
};
InitializeComponent();
}
}

WPF databinding binding error notification

OK, working on WPF(using MVVM) and came across a question, want some input. I have a simple class
like below(assume I have IDataErrorInfo implemented):
public class SimpleClassViewModel
{
DataModel Model {get;set;}
public int Fee {get { return Model.Fee;} set { Model.Fee = value;}}
}
I then try to bind to it in xaml:
<TextBox Text={Binding Fee, ValidatesOnDataErrors=true}/>
when a user then clears out the text, a databinding error occurs because it cant convert string.empty to int. Well, Fee is a required field, but because the databinding won't convert back I can't provide error information because my class isn't updated. So am I required to then do the following?
public class SimpleClassViewModel
{
DataModel Model {get;set;}
int? _Fee;
public int? Fee
{
get { return _Fee;}
set { _Fee = value;if (value.HasValue) { Model.Fee = value;}
}
}
This can be done using a ValueConverter:
using System.Windows.Data;
namespace MyNameSpace
{
class IntToStringConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int) value).ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int result;
var succes = int.TryParse((string) value,out result);
return succes ? result : 0;
}
}
}
You reference it in the XAML thus:
<Window xmlns:local="clr-namespace:MyNameSpace">
<Window.Resources>
<local:IntToStringConverter x:Key="IntConverter"/>
</Window.Resources>
<TextBox Text={Binding Fee, ValidatesOnDataErrors=true,
Converter={StaticResource IntConverter}}/>
</Window>
You can also take advantage of the fact you're doing MVVM and change the type of the Fee property to string. After all, your VM should provide a model that supports the view, and the view allows users to enter a string. Then you can provide a separate property that exposes the parsed fee as an int. That way your conversion logic is right there in the Fee property, making it easier to reuse, debug, and maintain.

WPF binding ComboBox to enum (with a twist)

Well the problem is that I have this enum, BUT I don't want the combobox to show the values of the enum. This is the enum:
public enum Mode
{
[Description("Display active only")]
Active,
[Description("Display selected only")]
Selected,
[Description("Display active and selected")]
ActiveAndSelected
}
So in the ComboBox instead of displaying Active, Selected or ActiveAndSelected, I want to display the DescriptionProperty for each value of the enum. I do have an extension method called GetDescription() for the enum:
public static string GetDescription(this Enum enumObj)
{
FieldInfo fieldInfo =
enumObj.GetType().GetField(enumObj.ToString());
object[] attribArray = fieldInfo.GetCustomAttributes(false);
if (attribArray.Length == 0)
{
return enumObj.ToString();
}
else
{
DescriptionAttribute attrib =
attribArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
So is there a way I can bind the enum to the ComboBox AND show it's content with the GetDescription extension method?
Thanks!
I would suggest a DataTemplate and a ValueConverter. That will let you customize the way it's displayed, but you would still be able to read the combobox's SelectedItem property and get the actual enum value.
ValueConverters require a lot of boilerplate code, but there's nothing too complicated here. First you create the ValueConverter class:
public class ModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return ((Mode) value).GetDescription();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
Since you're only converting enum values to strings (for display), you don't need ConvertBack -- that's just for two-way binding scenarios.
Then you put an instance of the ValueConverter into your resources, with something like this:
<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
<Window.Resources>
<WpfApplication1:ModeConverter x:Key="modeConverter"/>
</Window.Resources>
....
</Window>
Then you're ready to give the ComboBox a DisplayTemplate that formats its items using the ModeConverter:
<ComboBox Name="comboBox" ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
To test this, I threw in a Label too, that would show me the actual SelectedItem value, and it did indeed show that SelectedItem is the enum instead of the display text, which is what I would want:
<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>
I like the way you think. But GetCustomAttributes uses reflection. What is that going to do to your performance?
Check out this post:
WPF - Displaying enums in ComboBox control
http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html
This is how I am doing it with MVVM. On my model I would have defined my enum:
public enum VelocityUnitOfMeasure
{
[Description("Miles per Hour")]
MilesPerHour,
[Description("Kilometers per Hour")]
KilometersPerHour
}
On my ViewModel I expose a property that provides possible selections as string as well as a property to get/set the model's value. This is useful if we don't want to use every enum value in the type:
//UI Helper
public IEnumerable<string> VelocityUnitOfMeasureSelections
{
get
{
var units = new []
{
VelocityUnitOfMeasure.MilesPerHour.Description(),
VelocityUnitOfMeasure.KilometersPerHour.Description()
};
return units;
}
}
//VM property
public VelocityUnitOfMeasure UnitOfMeasure
{
get { return model.UnitOfMeasure; }
set { model.UnitOfMeasure = value; }
}
Furthermore, I use a generic EnumDescriptionCoverter:
public class EnumDescriptionConverter : IValueConverter
{
//From Binding Source
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
return (value as Enum).Description();
}
//From Binding Target
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string)) throw new ArgumentException("Value is not a string");
foreach(var item in Enum.GetValues(targetType))
{
var asString = (item as Enum).Description();
if (asString == (string) value)
{
return item;
}
}
throw new ArgumentException("Unable to match string to Enum description");
}
}
And finally, with the view I can do the following:
<Window.Resources>
<ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />
I suggest you use a markup extension I had already posted here, with just a little modification :
[MarkupExtensionReturnType(typeof(IEnumerable))]
public class EnumValuesExtension : MarkupExtension
{
public EnumValuesExtension()
{
}
public EnumValuesExtension(Type enumType)
{
this.EnumType = enumType;
}
[ConstructorArgument("enumType")]
public Type EnumType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.EnumType == null)
throw new ArgumentException("The enum type is not set");
return Enum.GetValues(this.EnumType).Select(o => GetDescription(o));
}
}
You can then use it like that :
<ComboBox ItemsSource="{local:EnumValues local:Mode}"/>
EDIT: the method I suggested will bind to a list of string, which is not desirable since we want the SelectedItem to be of type Mode. It would be better to remove the .Select(...) part, and use a binding with a custom converter in the ItemTemplate.
Questions of using reflection and attributes aside, there are a few ways you could do this, but I think the best way is to just create a little view model class that wraps the enumeration value:
public class ModeViewModel : ViewModel
{
private readonly Mode _mode;
public ModeViewModel(Mode mode)
{
...
}
public Mode Mode
{
get { ... }
}
public string Description
{
get { return _mode.GetDescription(); }
}
}
Alternatively, you could look into using ObjectDataProvider.
I've done it like this :
<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22" Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
in code I set itemSource :
CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))

Resources