Binding Doesn't Occur When re-Binding RadioButtons to an Enum - wpf

While looking at solutions for tying an enum to a group of RadioButtons, I discovered Sam's post from a year and a half ago.
Lars' answer was exactly what I was looking for: simple and effective.
Until I started changing the object tied to the RadioButton group. A simple version follows.
The XAML:
<Window x:Class="RadioEnum.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:re="clr-namespace:RadioEnum"
Height="200" Width="150">
<Window.DataContext>
<re:ViewModel />
</Window.DataContext>
<Window.Resources>
<re:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Window.Resources>
<DockPanel>
<ComboBox DockPanel.Dock="Top" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Things}" DisplayMemberPath="Name" />
<GroupBox>
<StackPanel>
<RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=First}">First</RadioButton>
<RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Second}">Second</RadioButton>
<RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Third}">Third</RadioButton>
</StackPanel>
</GroupBox>
</DockPanel>
</Window>
Now, the C#:
namespace RadioEnum
{
public class ViewModel {
public ObservableCollection<Thing> Things { get; set; }
public ViewModel() {
Things = new ObservableCollection<Thing> {
new Thing{ Name = "Thing1", Choice = Choice.First, },
new Thing{ Name = "Thing2", Choice = Choice.Second, },
};
}
}
public class Thing {
public string Name { get; set; }
public Choice Choice { get; set; }
}
public enum Choice { None, First, Second, Third, }
public class EnumBooleanConverter : IValueConverter {
// Yes, there are slight differences here from Lars' code, but that
// was to ease debugging. The original version has the same symptom.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
object ret = DependencyProperty.UnsetValue;
var parameterString = parameter as string;
if (parameterString != null && Enum.IsDefined(value.GetType(), value)) {
object parameterValue = Enum.Parse(value.GetType(), parameterString);
ret = parameterValue.Equals(value);
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
object ret = DependencyProperty.UnsetValue;
var parameterString = parameter as string;
if (parameterString != null && !value.Equals(false))
ret = Enum.Parse(targetType, parameterString);
return ret;
}
}
}
When the application loads with Thing1 in the ComboBox, the correct Choice is selected in the radio group. Selecting Thing2 from the ComboBox correctly updates the Choice. But, from this point, switching no longer updates the binding to the Second RadioButton and thus no longer calls the Convert method with parameter set to "Second".
In other words, although Thing2's values have not changed, all of the RadioButtons are cleared from that point forward. Thing1 continues to work, though.
There are no errors seen - neither exceptions nor messages in the Output window. I've tried binding in different ways. I tried making Choice a DependencyProperty, too (and Thing then a DependencyObject).
Any insights out there?

Original Response:
Not sure if this will fix your problem or not... as I think the break in the binding might be somewhere with your combobox... but to improve on your EnumConverter and make sure it's working properly... I suggest taking a look at my response to this question:
How to bind RadioButtons to an enum?
(Not the selected answer... but my response with the generic converter rather than converting string values)
Edit:
I just took your code and tried it and everything seemed to work great! (using visual studio 2010 .net 4)
You have a list of Things (in your combobox) and can set the currently selected Thing's choice via radio button. I can modify each Thing's choice and when I switch between Things it correctly updates the radio button for me!
Correct me if I am wrong on the desired functionality:
App Loads - ComboBox: Thing1 RadioButton: First
Select Thing2 - ComboBox: Thing2 RadioButton: Second
Select Thing1 - ComboBox: Thing1 RadioButton: First
Select Third - ComboBox: Thing1 RadioButton: Third
Select Thing2 - ComboBox: Thing2 RadioButton: Second
Select First - ComboBox: Thing2 RadioButton: First
Select Thing1 - ComboBox: Thing1 RadioButton: Third
Select Thing2 - ComboBox: Thing2 RadioButton: First
Above is the functionality I get when running your app with the code you provided (and with the modified EnumConverter). This also appears to be the desired result. Is the above correct and does that not work that way for you?
Edit 2: I can confirm that the issue is with .NET 3.5
I run .NET 4 Client profile... everything works as desired... running .NET 3.5 Client profile... I get the result you stated.

For those of you who may be stuck with doing this in .NET 3.5, I do have something working. It's not nearly as elegant as the code above, but it functions.
I'm more than happy to see some feedback from others on alternative methods, too. The example code below is for a ThingB that functions in both .NET 3.5 and 4.
First, change the XAML on the RadioButtons as follows (note that the GroupName must be different for each):
<RadioButton GroupName="One" IsChecked="{Binding Path=Things/ChoiceOne}">First</RadioButton>
<RadioButton GroupName="Two" IsChecked="{Binding Path=Things/ChoiceTwo}">Second</RadioButton>
<RadioButton GroupName="Three" IsChecked="{Binding Path=Things/ChoiceThree}">Third</RadioButton>
Second, the ThingB code:
public class ThingB : INotifyPropertyChanged {
public string Name { get; set; }
public Choice Choice {
get {
return choiceOne ? Choice.First
: choiceTwo ? Choice.Second
: choiceThree ? Choice.Third : Choice.None;
}
set {
choiceOne = Choice.First.Equals(value);
choiceTwo = Choice.Second.Equals(value);
choiceThree = Choice.Third.Equals(value);
}
}
private bool choiceOne;
public bool ChoiceOne {
get { return choiceOne; }
set {
if(value) {
Choice = Choice.First;
NotifyChoiceChanged();
}
}
}
private bool choiceTwo;
public bool ChoiceTwo {
get { return choiceTwo; }
set {
if (value) {
Choice = Choice.Second;
NotifyChoiceChanged();
}
}
}
private bool choiceThree;
public bool ChoiceThree {
get { return choiceThree; }
set {
if (value) {
Choice = Choice.Third;
NotifyChoiceChanged();
}
}
}
private void NotifyChoiceChanged() {
OnPropertyChanged("ChoiceOne");
OnPropertyChanged("ChoiceTwo");
OnPropertyChanged("ChoiceThree");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}

Related

Bind the enum System.IO.WatcherChangeTypes to a ComboBox Itemsource [duplicate]

I am trying to find a simple example where the enums are shown as is. All examples I have seen tries to add nice looking display strings but I don't want that complexity.
Basically I have a class that holds all the properties that I bind, by first setting the DataContext to this class, and then specifying the binding like this in the xaml file:
<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>
But this doesn't show the enum values in the ComboBox as items.
You can do it from code by placing the following code in Window Loaded event handler, for example:
yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();
If you need to bind it in XAML you need to use ObjectDataProvider to create object available as binding source:
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects">
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="StyleAlias:EffectStyle"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
SelectedItem="{Binding Path=CurrentEffectStyle}" />
</Grid>
</Window>
Draw attention on the next code:
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"
Guide how to map namespace and assembly you can read on MSDN.
I like for all objects that I'm binding to be defined in my ViewModel, so I try to avoid using <ObjectDataProvider> in the xaml when possible.
My solution uses no data defined in the View and no code-behind. Only a DataBinding, a reusable ValueConverter, a method to get a collection of descriptions for any Enum type, and a single property in the ViewModel to bind to.
When I want to bind an Enum to a ComboBox the text I want to display never matches the values of the Enum, so I use the [Description()] attribute (from System.ComponentModel) to give it the text that I actually want to see in the ComboBox. If I had an enum of days of the week, it would look something like this:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
First I created helper class with a couple methods to deal with enums. One method gets a description for a specific value, the other method gets all values and their descriptions for a type.
public static class EnumHelper
{
public static string Description(this Enum value)
{
var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute).Description;
// If no description is found, the least we can do is replace underscores with spaces
// You can add your own custom default formatting logic here
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
}
}
Next, we create a ValueConverter. Inheriting from MarkupExtension makes it easier to use in XAML so we don't have to declare it as a resource.
[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
My ViewModel only needs 1 property that my View can bind to for both the SelectedValue and ItemsSource of the combobox:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
And finally to bind the ComboBox view (using the ValueConverter in the ItemsSource binding)...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
To implement this solution you only need to copy my EnumHelper class and EnumToCollectionConverter class. They will work with any enums. Also, I didn't include it here, but the ValueDescription class is just a simple class with 2 public object properties, one called Value, one called Description. You can create that yourself or you can change the code to use a Tuple<object, object> or KeyValuePair<object, object>
For those who wanted to see the ValueDescription class:
public class ValueDescription
{
public object Value {get; set};
public object Description {get; set};
}
I used another solution using MarkupExtension.
I made class which provides items source:
public class EnumToItemsSource : MarkupExtension
{
private readonly Type _type;
public EnumToItemsSource(Type type)
{
_type = type;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Enum.GetValues(_type)
.Cast<object>()
.Select(e => new { Value = (int)e, DisplayName = e.ToString() });
}
}
That's almost all... Now use it in XAML:
<ComboBox DisplayMemberPath="DisplayName"
ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
SelectedValue="{Binding Path=WhereEverYouWant}"
SelectedValuePath="Value" />
Change 'enums:States' to your enum
Use ObjectDataProvider:
<ObjectDataProvider x:Key="enumValues"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExampleEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
and then bind to static resource:
ItemsSource="{Binding Source={StaticResource enumValues}}"
based on this article
Nick's answer has really helped me, but I realised it could be tweaked slightly, to avoid an extra class, ValueDescription.
I remembered that there exists a KeyValuePair class already in the framework, so this can be used instead.
The code changes only slightly :
public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("TEnum must be an Enumeration type");
}
return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
select new KeyValuePair<string, string>(e.ToString(), e.Description());
}
public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
get
{
return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
}
}
and finally the XAML :
<ComboBox ItemSource="{Binding Path=PlayerClassList}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=SelectedClass}" />
I hope this is helpful to others.
You'll need to create an array of the values in the enum, which can be created by calling System.Enum.GetValues(), passing it the Type of the enum that you want the items of.
If you specify this for the ItemsSource property, then it should be populated with all of the enum's values. You probably want to bind SelectedItem to EffectStyle (assuming it is a property of the same enum, and contains the current value).
There are many excellent answers to this question and I humbly submit mine. I find that mine is somewhat simpler and more elegant. It requires only a value converter.
Given an enum...
public enum ImageFormat
{
[Description("Windows Bitmap")]
BMP,
[Description("Graphics Interchange Format")]
GIF,
[Description("Joint Photographic Experts Group Format")]
JPG,
[Description("Portable Network Graphics Format")]
PNG,
[Description("Tagged Image Format")]
TIFF,
[Description("Windows Media Photo Format")]
WDP
}
and a value converter...
public class ImageFormatValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ImageFormat format)
{
return GetString(format);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string s)
{
return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':')));
}
return null;
}
public string[] Strings => GetStrings();
public static string GetString(ImageFormat format)
{
return format.ToString() + ": " + GetDescription(format);
}
public static string GetDescription(ImageFormat format)
{
return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute<DescriptionAttribute>().Description;
}
public static string[] GetStrings()
{
List<string> list = new List<string>();
foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
{
list.Add(GetString(format));
}
return list.ToArray();
}
}
resources...
<local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>
XAML declaration...
<ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>
View model...
private ImageFormat _imageFormat = ImageFormat.JPG;
public ImageFormat Format
{
get => _imageFormat;
set
{
if (_imageFormat != value)
{
_imageFormat = value;
OnPropertyChanged();
}
}
}
Resulting combobox...
It works very nice and simple.
xaml
<ComboBox ItemsSource="{Binding MyEnumArray}">
.cs
public Array MyEnumArray
{
get { return Enum.GetValues(typeof(MyEnum)); }
}
All the above posts have missed a simple trick. It is possible from the binding of SelectedValue to find out how to populate the ItemsSource AUTOMAGICALLY so that your XAML markup is just.
<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>
For example in my ViewModel I have
public enum FoolEnum
{
AAA, BBB, CCC, DDD
};
FoolEnum _Fool;
public FoolEnum Fool
{
get { return _Fool; }
set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
}
ValidateRaiseAndSetIfChanged is my INPC hook. Yours may differ.
The implementation of EnumComboBox is as follows but first I'll need a little helper to get my enumeration strings and values
public static List<Tuple<object, string, int>> EnumToList(Type t)
{
return Enum
.GetValues(t)
.Cast<object>()
.Select(x=>Tuple.Create(x, x.ToString(), (int)x))
.ToList();
}
and the main class ( Note I'm using ReactiveUI for hooking property changes via WhenAny )
using ReactiveUI;
using ReactiveUI.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Documents;
namespace My.Controls
{
public class EnumComboBox : System.Windows.Controls.ComboBox
{
static EnumComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox)));
}
protected override void OnInitialized( EventArgs e )
{
base.OnInitialized(e);
this.WhenAnyValue(p => p.SelectedValue)
.Where(p => p != null)
.Select(o => o.GetType())
.Where(t => t.IsEnum)
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(FillItems);
}
private void FillItems(Type enumType)
{
List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();
foreach (var idx in EnumUtils.EnumToList(enumType))
{
values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2));
}
this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList();
UpdateLayout();
this.ItemsSource = values;
this.DisplayMemberPath = "Value";
this.SelectedValuePath = "Key";
}
}
}
You also need to set the style correctly in Generic.XAML or your box won't render anything and you will pull your hair out.
<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>
and that is that. This could obviously be extended to support i18n but would make the post longer.
Universal apps seem to work a bit differently; it doesn't have all the power of full-featured XAML. What worked for me is:
I created a list of the enum values as the enums (not converted to
strings or to integers) and bound the ComboBox ItemsSource to that
Then I could bind the ComboBox ItemSelected to my public property
whose type is the enum in question
Just for fun I whipped up a little templated class to help with this and published it to the MSDN Samples pages. The extra bits let me optionally override the names of the enums and to let me hide some of the enums. My code looks an awful like like Nick's (above), which I wish I had seen earlier.
If you are binding to an actual enum property on your ViewModel, not a int representation of an enum, things get tricky. I found it is necessary to bind to the string representation, NOT the int value as is expected in all of the above examples.
You can tell if this is the case by binding a simple textbox to the property you want to bind to on your ViewModel. If it shows text, bind to the string. If it shows a number, bind to the value. Note I have used Display twice which would normally be an error, but it's the only way it works.
<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
DisplayMemberPath="Display"
SelectedValuePath="Display"
ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />
Greg
public class EnumItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!value.GetType().IsEnum)
return false;
var enumName = value.GetType();
var obj = Enum.Parse(enumName, value.ToString());
return System.Convert.ToInt32(obj);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.ToObject(targetType, System.Convert.ToInt32(value));
}
}
You should extend Rogers and Greg's answer with such kind of Enum value converter, if you're binding straight to enum object model properties.
I liked tom.maruska's answer, but I needed to support any enum type which my template might encounter at runtime. For that, I had to use a binding to specify the type to the markup extension. I was able to work in this answer from nicolay.anykienko to come up with a very flexible markup extension which would work in any case I can think of. It is consumed like this:
<ComboBox SelectedValue="{Binding MyEnumProperty}"
SelectedValuePath="Value"
ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}"
DisplayMemberPath="DisplayName" />
The source for the mashed up markup extension referenced above:
class EnumToObjectArray : MarkupExtension
{
public BindingBase SourceEnum { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
DependencyProperty targetProperty;
if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
{
targetObject = (DependencyObject)target.TargetObject;
targetProperty = (DependencyProperty)target.TargetProperty;
}
else
{
return this;
}
BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum);
var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType();
if (type.BaseType != typeof(System.Enum)) return this;
return Enum.GetValues(type)
.Cast<Enum>()
.Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) });
}
private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum)
, typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Extension method which returns the string specified in the Description attribute, if any. Oherwise, name is returned.
/// </summary>
/// <param name="value">The enum value.</param>
/// <returns></returns>
public static string Description(Enum value)
{
var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs.Any())
return (attrs.First() as DescriptionAttribute).Description;
//Fallback
return value.ToString().Replace("_", " ");
}
}
Simple and clear explanation:
http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/
xmlns:local="clr-namespace:BindingEnums"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
...
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:Status"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
...
<Grid>
<ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
Using ReactiveUI, I've created the following alternate solution. It's not an elegant all-in-one solution, but I think at the very least it's readable.
In my case, binding a list of enum to a control is a rare case, so I don't need to scale the solution across the code base. However, the code can be made more generic by changing EffectStyleLookup.Item into an Object. I tested it with my code, no other modifications are necessary. Which means the one helper class could be applied to any enum list. Though that would reduce its readability - ReactiveList<EnumLookupHelper> doesn't have a great ring to it.
Using the following helper class:
public class EffectStyleLookup
{
public EffectStyle Item { get; set; }
public string Display { get; set; }
}
In the ViewModel, convert the list of enums and expose it as a property:
public ViewModel : ReactiveObject
{
private ReactiveList<EffectStyleLookup> _effectStyles;
public ReactiveList<EffectStyleLookup> EffectStyles
{
get { return _effectStyles; }
set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
}
// See below for more on this
private EffectStyle _selectedEffectStyle;
public EffectStyle SelectedEffectStyle
{
get { return _selectedEffectStyle; }
set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
}
public ViewModel()
{
// Convert a list of enums into a ReactiveList
var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
.Select( x => new EffectStyleLookup() {
Item = x,
Display = x.ToString()
});
EffectStyles = new ReactiveList<EffectStyle>( list );
}
}
In the ComboBox, utilise the SelectedValuePath property, to bind to the original enum value:
<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />
In the View, this allows us to bind the original enum to the SelectedEffectStyle in the ViewModel, but display the ToString() value in the ComboBox:
this.WhenActivated( d =>
{
d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});
I'm adding my comment (in VB, sadly, but the concept can be easily replicated over to C# in a heartbeat), because I just had to reference this and didn't like any of the answers as they were too complex. It shouldn't have to be this difficult.
So I came up with an easier way. Bind the Enumerators to a Dictionary. Bind that dictionary to the Combobox.
My combobox:
<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2"
Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104"
SelectedValuePath="Key" DisplayMemberPath="Value" />
My code-behind. Hopefully, this helps someone else out.
Dim tDict As New Dictionary(Of Integer, String)
Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType))
For Each x As Helper.Enumerators.AllowedType In types
Dim z = x.ToString()
Dim y = CInt(x)
tDict.Add(y, z)
Next
cmbRole.ClearValue(ItemsControl.ItemsSourceProperty)
cmbRole.ItemsSource = tDict
I wouldn't recommend implementing this as it is but hopefully this can inspire a good solution.
Let's say your enum is Foo. Then you can do something like this.
public class FooViewModel : ViewModel
{
private int _fooValue;
public int FooValue
{
get => _fooValue;
set
{
_fooValue = value;
OnPropertyChange();
OnPropertyChange(nameof(Foo));
OnPropertyChange(nameof(FooName));
}
}
public Foo Foo
{
get => (Foo)FooValue;
set
{
_fooValue = (int)value;
OnPropertyChange();
OnPropertyChange(nameof(FooValue));
OnPropertyChange(nameof(FooName));
}
}
public string FooName { get => Enum.GetName(typeof(Foo), Foo); }
public FooViewModel(Foo foo)
{
Foo = foo;
}
}
Then on Window.Load method you can load all enums to an ObservableCollection<FooViewModel> which you can set as the DataContext of the combobox.
I just kept it simple. I created a list of items with the enum values in my ViewModel:
public enum InputsOutputsBoth
{
Inputs,
Outputs,
Both
}
private IList<InputsOutputsBoth> _ioTypes = new List<InputsOutputsBoth>()
{
InputsOutputsBoth.Both,
InputsOutputsBoth.Inputs,
InputsOutputsBoth.Outputs
};
public IEnumerable<InputsOutputsBoth> IoTypes
{
get { return _ioTypes; }
set { }
}
private InputsOutputsBoth _selectedIoType;
public InputsOutputsBoth SelectedIoType
{
get { return _selectedIoType; }
set
{
_selectedIoType = value;
OnPropertyChanged("SelectedIoType");
OnSelectionChanged();
}
}
In my xaml code I just need this:
<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
<Window.Resources>
<ObjectDataProvider x:Key="DiaryTypeEnum"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="z:Enums+DiaryType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource DiaryTypeEnum}}" SelectedItem="{x:Static z:Enums+DiaryType.Defect}" />
Where z its xmlns:z="clr-namespace:ProjName.Helpers"
My Enum into static class
public static class Enums
{
public enum DiaryType
{
State,
Defect,
Service,
Other
}
public enum OtherEnumOrMethods
{
//TODO
}
}
Nick's solution can be simplified more, with nothing fancy, you would only need a single converter:
[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var r = Enum.GetValues(value.GetType());
return r;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
You then use this wherever you want your combo box to appear:
<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}" SelectedItem="{Binding PagePosition}" />
here is my short answer.
public enum Direction { Left, Right, Up, Down };
public class Program
{
public Direction ScrollingDirection { get; set; }
public List<string> Directions { get; } = new List<string>();
public Program()
{
loadListDirection();
}
private void loadListDirection()
{
Directions.AddRange(Enum.GetNames(typeof(Direction)));
}
}
And Xaml:
<ComboBox SelectedIndex="0" ItemsSource="{Binding Path=Directions, Mode=OneWay}" SelectedItem="{Binding Path=ScrollingDirection, Mode=TwoWay}"/>
Good Luck!

MVVM IValueConverter Convert method getting empty string argument when expecting a float

This may get a tad long, but here goes. I have created a small, wizard-style sample app using the MVVM pattern (basically a dumbed-down version of the code in my "real" app). In this app, the main window moves from through a List<..> of view models, with each view model displaying its associated view. I have 2 view model classes that are essentially identical, and they display the same view.
On the view is a combo box, populated with an array of float. The SelectedItem is bound to a float property on the view model. I have created a template for the combo box to display each item as a TextBlock, with the text taking the float value and going through a value converter.
The problem, when I switch back and forth between view models, all works fine as long as every view model I switch to is of the same class. As soon as I change the current page to an instance of a different view model, the value converter's Convert gets called with a 'value' parameter that is a zero-length string. Up til then, Convert was only being called with a float, as I would expect.
My question : why is the converter being called with the empty string ONLY in the case of switching view model classes?
I am attaching the main window XAML and view model, as well as the view/view models displayed for each "page". You'll notice that the main window view model has a list containing 2 instances of PageViewModel and 2 instances of OtherViewModel. I can switch back and forth between the first 2 fine, and the value converter only gets called with a float value. Once I switch to the first OtherViewModel instance, the converter gets an "extra" call with an empty string as the value.
Code snippets :
MainWindow
<Grid.Resources>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<local:PageView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:OtherViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
<!-- Page -->
<ContentControl Margin="5,5,5,35"
Height="100"
IsTabStop="False"
Content="{Binding CurrentPage}" />
<!-- Commands -->
<Button Margin="5,115,0,0"
Width="75"
Content="< Back"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding BackCommand}" />
<Button Margin="85,115,0,0"
Width="75"
Content="Next >"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding NextCommand}" />
MainWindowViewModel
public MainWindowViewModel()
{
m_pages = new List<BaseViewModel>();
m_pages.Add(new PageViewModel(1, 7f));
m_pages.Add(new PageViewModel(2, 8.5f));
m_pages.Add(new OtherViewModel(3, 10f));
m_pages.Add(new OtherViewModel(4, 11.5f));
m_currentPage = m_pages.First();
m_nextCommand = new BaseCommand(param => this.OnNext(), param => this.EnableNext());
m_backCommand = new BaseCommand(param => this.OnBack(), param => this.EnableBack());
}
// Title
public string Title
{
get
{
return (CurrentPage != null) ? CurrentPage.Name : Name;
}
}
// Pages
BaseViewModel m_currentPage = null;
List<BaseViewModel> m_pages = null;
public BaseViewModel CurrentPage
{
get
{
return m_currentPage;
}
set
{
if (value == m_currentPage)
return;
m_currentPage = value;
OnPropertyChanged("Title");
OnPropertyChanged("CurrentPage");
}
}
// Back
ICommand m_backCommand = null;
public ICommand BackCommand
{
get
{
return m_backCommand;
}
}
public void OnBack()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) - 1];
}
public bool EnableBack()
{
return CurrentPage != m_pages.First();
}
// Next
ICommand m_nextCommand = null;
public ICommand NextCommand
{
get
{
return m_nextCommand;
}
}
public void OnNext()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) + 1];
}
public bool EnableNext()
{
return CurrentPage != m_pages.Last();
}
}
Notice the 2 instance of one view model followed by 2 instances of the other.
PageView
<Grid.Resources>
<x:Array x:Key="DepthList"
Type="sys:Single">
<sys:Single>7</sys:Single>
<sys:Single>8.5</sys:Single>
<sys:Single>10</sys:Single>
<sys:Single>11.5</sys:Single>
</x:Array>
<local:MyConverter x:Key="MyConverter" />
</Grid.Resources>
<TextBlock Text="Values:"
Margin="5,5,0,0">
</TextBlock>
<ComboBox Width="100"
Height="23"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="5,25,0,0"
DataContext="{Binding}"
SelectedItem="{Binding Depth}"
ItemsSource="{Binding Source={StaticResource DepthList}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
PageViewModel/OtherViewModel/MyConverter
public class PageViewModel : BaseViewModel
{
public PageViewModel(int index, float depth)
{
Depth = depth;
Name = "Page #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
public class OtherViewModel : BaseViewModel
{
public OtherViewModel(int index, float depth)
{
Depth = depth;
Name = "Other #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
[ValueConversion(typeof(DateTime), typeof(String))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("IValueConverter.Convert : received a " + value.GetType().Name);
string text = "";
if (value is float)
{
text = value.ToString();
}
else
{
throw new ArgumentException("MyConverter : input value is NOT a float.");
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return float.Parse(value as string);
}
}
Note: I can remove the exception in the Convert method, and everything seems to work fine. But, I would like to know why this is happening. Why is the converter getting an empty string instead of the expected float, and only when we switch view models?
Any insights would be greatly appreciated. Thanks in advance...
Joe
I've had the same issue (with an enum instead of a float).
When the View is closed the ComboBox Selection is emptied. You can check this by handling SelectionChanged event and inspecting the SelectionChangedEventArgs RemovedItems collection.
This ends in String.Empty being passed into your ValueConverter.
In my case, I have modified the ValueConverter.Convert to allow string.Empty as a valid value, and return string.Empty.
This is the code I used:
// When view is unloaded, ComboBox Selection is emptied and Convert is passed string.Empty
// Hence we need to handle this conversion
if (value is string && string.IsNullOrEmpty((string)value))
{
return string.Empty;
}
Try
public BaseViewModel
{
public virtual float Depth{get;set;}
...
}
Then
public class PageViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
and
public class OtherViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
Then you only need one DataTemplate
<Grid.Resources>
<DataTemplate DataType="{x:Type local:BaseViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
I'm guessing the strange value being passed to the converter is due to DataTemplates being switched.
Not tested

Binding to individual elements in a collection

I am fairly new to MVVM, so bear with. I have a view model class that has a public property implemented as so:
public List<float> Length
{
get;
set;
}
In my XAML for the view, I have several text boxes, with each one bound to a specific element in this Length list:
<TextBox Text="{Binding Length[0], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[2], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[4], Converter=DimensionConverter}" />
The DimensionConverter is a IValueConverter derived class that formats the values like a dimension (i.e. 480.0 inches becomes 40'0" in the text box on screen), and back again (i.e. takes 35'0" for a string and yield 420.0 inches for the source)
My issue: I need to be able to validate each value in the List as it is changed in the associated TextBox. For some, I may need to modify other values in the List depending on the entered value (i.e. change the float at Length[0] will change the value at Length[4] and update the screen).
Is there any way to re-work the property to allow for an indexer? Or, do I need to create individual properties for each item in the List (which really makes the List unnecessary)? Essentially, since I already have the collection of float, I was hoping to be able to write MVVM code to validate each item as it is modified.
Thoughts? (and, thanks in advance)
You can use an ObservableCollection<float> instead of a List<float>, and handle the CollectionChanged event to detect when the user changes a value.
Wouldn't something like this:
<ItemsControl ItemsSource="{Binding Length}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Be close to what you want?
It will display the entire list, and allow the user to modify the values, which will be returned straight back to the list, as long as your IValueConverter implements ConvertBack.
Then do as Thomas said to validate, or implement an ObservableLinkedList
What you do at the moment looks dirty already and it's barely a few lines of code..
It would be great if you can have a class which implements INotifyPropertyChanged to have the properties provided the length of list is constant.
if you want validate your text input with mvvm then create a model that you can youse at your viewmodel
public class FloatClass : INotifyPropertyChanged
{
private ICollection parentList;
public FloatClass(float initValue, ICollection pList) {
parentList = pList;
this.Value = initValue;
}
private float value;
public float Value {
get { return this.value; }
set {
if (!Equals(value, this.Value)) {
this.value = value;
this.RaiseOnPropertyChanged("Value");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
at your viewmodel you can use the model like this
public class FloatClassViewmModel : INotifyPropertyChanged
{
public FloatClassViewmModel() {
this.FloatClassCollection = new ObservableCollection<FloatClass>();
foreach (var floatValue in new[]{0f,1f,2f,3f}) {
this.FloatClassCollection.Add(new FloatClass(floatValue, this.FloatClassCollection));
}
}
private ObservableCollection<FloatClass> floatClassCollection;
public ObservableCollection<FloatClass> FloatClassCollection {
get { return this.floatClassCollection; }
set {
if (!Equals(value, this.FloatClassCollection)) {
this.floatClassCollection = value;
this.RaiseOnPropertyChanged("FloatClassCollection");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
here is the xaml example
<ItemsControl ItemsSource="{Binding Path=FloatClassViewmModel.FloatClassCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
hope this helps

Selection bug for listbox where the list items are value types / structs and contain duplicates?

I turned an Horizontal ItemsControl to a Listbox so that I am able to select individual items but found that the selection was broken. Took some time to distill out the problematic bit.
Books = new[] { new Book{Id=1, Name="Book1"},
new Book{Id=2, Name="Book2"},
new Book{Id=3, Name="Book3"},
new Book{Id=4, Name="Book4"},
new Book{Id=3, Name="Book3"},
};
<DataTemplate DataType="{x:Type WPF_Sandbox:Book}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<ListBox ItemsSource="{Binding Books}"/>
If Book is a struct, the listbox selection (default mode : single) goes awry if you select an item which has an equivalent struct in the list. e.g Book3
If Book is turned into a class (with non-value type semantics), selection is fixed.
Choices (so far, don't like any of them):
I chose structs because its a small data structure and the value type semantics are useful in comparing 2 instances for equality. Changing it to a class causes me to lose value-type semantics.. I can't use the default Equals anymore or override it for memberwise comparison.
Add a differentiating Book attribute purely for the listbox selection to work (e.g. an Index).
Eliminate Duplicates.. Not possible.
WPF listbox : problem with selection : states that the Listbox is setting SelectedItem and while updating the UI for this, it just lights up all items in the list that Equal(SelectedItem). Not sure why.. highlighting SelectedIndex would make this problem go away; maybe I am missing something.
ListBox is selecting many items even in SelectionMode="Single" : shows the same problem when list items are strings (value type semantics)
Why not simply use a better collection class as your datasource to overcome the problem
var collection = new[]
{
new Book {Id = 1, Name = "Book1"},
new Book {Id = 2, Name = "Book2"},
new Book {Id = 3, Name = "Book3"},
new Book {Id = 4, Name = "Book4"},
new Book {Id = 3, Name = "Book3"},
};
var Books = collection.ToDictionary(b => Guid.NewGuid(), b => b);
DataContext = Books;
And this will be your DataTemplate
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value.Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
I'm not clear on why you have duplicates in your list, if they're absolutely identical (i.e., if duplicates have all the same content and return true from Equals). You won't have any way to tell which of the duplicates the user has selected. Neither will the ListBox, which is probably why you're having problems.
Maybe, instead of binding directly to a collection of structs, you could wrap each struct in a class? Just define a BookWrapper class that contains a Book struct, and bind to a collection of BookWrappers instead of a collection of Books. You fix the problem of WPF not being able to tell the instances apart, but the rest of your code could continue to have the benefits of a struct.
Thanks to Dean Chalk for his idea.
I extend it so that it is easier to user for other structs
The idea is to use a converter to cast the original struct collection to a custom collection, which in turn override the equal to compare with Guid ID. You still has the original order
public class StructListItem
{
private Guid _id = Guid.NewGuid();
public Guid ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
private object _core = default(object);
public object Core
{
get
{
return _core;
}
set
{
_core = value;
}
}
public StructListItem(object core)
{
Core = core;
}
public override bool Equals(object obj)
{
return ID.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
}
public class StructToCollConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is IEnumerable)
{
List<StructListItem> _ret = new List<StructListItem>();
if (value != null)
{
IEnumerator i = ((IEnumerable)value).GetEnumerator();
while (i.MoveNext())
{
_ret.Add(new StructListItem(i.Current));
}
}
return _ret.ToArray();
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
<ListBox ItemsSource="{Binding Books, Converter={StaticResource converter}}" SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Core.Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Garyx
Something a bit simpler maybe ?
public class StructListItem<T> where T : struct
{
public T Item { get; private set; }
public readonly Guid Id = Guid.NewGuid();
public StructListItem(T item)
{
Item = item;
}
public static IEnumerable<StructListItem<U>>
GetStructList<U>(IEnumerable<U> originalList) where U : struct
{
return originalList.Select(i => new StructListItem<U>(i));
}
}

Problem in DataBinding an Enum using dictionary approach to a combobox in WPF

I have a Dictionary which is binded to a combobox. I have used dictionary to provide spaces in enum.
public enum Option {Enter_Value, Select_Value};
Dictionary<Option,string> Options;
<ComboBox
x:Name="optionComboBox"
SelectionChanged="optionComboBox_SelectionChanged"
SelectedValuePath="Key"
DisplayMemberPath="Value"
SelectedItem="{Binding Path = SelectedOption}"
ItemsSource="{Binding Path = Options}" />
This works fine.
My queries:
1. I am not able to set the initial value to a combo box.
In above XAML snippet the line
SelectedItem="{Binding Path = SelectedOption}"
is not working. I have declared SelectOption in my viewmodel. This is of type string and I have intialized this string value in my view model as below:
SelectedOption = Options[Options.Enter_Value].ToString();
2. The combobox is binded to datadictionary which have two options first is "Enter_value" and second is "Select_value" which is actually Option enum.
Based on the Option enum value I want to perform different action.
For example
if option is equal to option.Enter_value then
Combo box becomes editable and user can enter the numeric value in it.
if option is equal to option.Select_value value then
the value comes from the database and the combo box becomes read only and shows the fetched value from the database.
Please Help!!
Try binding SelectedValue, not SelectedItem if SelectedOption is of type Option.
About your second question: Based on selection you can hide your ComboBox and display a TextBlock or TextBox in it's place. Or you can use RadioButtons and enable or disable input accordingly.
Your problem, probably, is that you've bound SelectedItem to a property of the wrong type.
An ItemsControl iterates over its ItemsSource's enumerator to build its list of items. The enumerator for your dictionary is of type KeyValuePair<Option, string>. So your SelectedOption property must also be of that type - if you look in the Output window when your application is running, you'll probably see a data-binding error to that effect there.
I can't understand your second question.
Edit
Okay, it's a lot easier to just provide a working example than to explain why code that I can't see isn't working.
First, you need a view model class that implements INotifyPropertyChanged and that exposes SelectedItem, Value, and IsValueReadOnly properties, and that correctly raises PropertyChanged events for those properties when the selected item changes.
public enum Option
{
EditOption,
OtherOption
} ;
public class MyViewModel : INotifyPropertyChanged
{
private Dictionary<Option, string> _Items;
private KeyValuePair<Option, string> _SelectedItem;
private string _Value;
public MyViewModel()
{
_Items = new Dictionary<Option, string>
{
{Option.EditOption, "Editable value"},
{Option.OtherOption, "Other value"}
};
}
public Dictionary<Option, string> Items
{
get { return _Items; }
}
public KeyValuePair<Option, string> SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
OnPropertyChanged("IsValueReadOnly");
OnPropertyChanged("Value");
}
}
public bool IsValueReadOnly
{
get { return _SelectedItem.Key != Option.EditOption; }
}
public string Value
{
get { return IsValueReadOnly ? "Read-only" : _Value; }
set { _Value = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now the XAML for your ComboBox and TextBox looks like this:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication6="clr-namespace:WpfApplication6"
Title="MainWindow">
<Window.DataContext>
<WpfApplication6:MyViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Items}"
DisplayMemberPath="Key"
SelectedItem="{Binding SelectedItem}"/>
<TextBox Text="{Binding Value}"
IsReadOnly="{Binding IsValueReadOnly}"/>
</StackPanel>
</Window>

Resources