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!
I have a text box like
<TextBox Text="{Binding TransactionDetails.TransactionAmount, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="5"
x:Name="TextBoxAmount"/>
And I have taken "TransactionAmount" as Double. Its working well on integer value but when I am typing some Floating point value like 100.456 I am not able to type '.'
You are updating your property every time the value changes. When you type in a ., it is written into your viewmodel and the view is updated.
e.g. if you type in 100. it is rounded to 100, thus you won't see any dot ever.
You have some options to change this behavior:
use a deferred binding:
<TextBox Text="{Binding Path=TransactionDetails.TransactionAmount,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Delay=250}"
Grid.Column="3"
Grid.ColumnSpan="2"
Grid.Row="5"
x:Name="TextBoxAmount" />
only change the value if it is different from the saved one
(I'd recommend this for every binding):
private double _transactionAmount;
public double TransactionAmount
{
get { return _transactionAmount; }
set
{
if (_transactionAmount != value)
{
_transactionAmount = value;
Notify("TransactionAmount");
}
}
or use some kind of validation, e.g. ValidatesOnExceptions.
The best solution I got by using StringFormat like
<TextBox Text="{Binding TransactionDetails.TransactionAmount, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,StringFormat=N2}" Grid.Column="3"
Grid.ColumnSpan="2" Grid.Row="5" x:Name="TextBoxAmount" />
Also we can go for custom string format as per requirements
The behavior is as expected when your app targets .NET 4.0 or earlier but was changed later on (MSDN). The old behavior can be restored by setting:
System.Windows.FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
Set this as early as possible (e.g. in the constructor of App.cs), otherwise the framework raises an exception.
Source and detailed explanation: https://www.mobilemotion.eu/?p=1855
Your Problem is with UpdateSourceTrigger.
Instead of using there You can use something like this,
private double amount;
public double Amount
{
get
{
return amount;
}
set
{
amount= value;
PropertyChanged();
Calculation();
}
}
PropertyChanged() You will get this from INotifyPropertyChanged. For more Information click here
https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
you can replace the default converter with this class when binding to double type source data. This is more convient to use. below is the code:
public class double2txtConverter : IValueConverter
{
string _strCache;
double _dCache;
//Convert double to string of textbox.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (_dCache == (double)value)
return _strCache;
else
return value.ToString();
}
//convert string to double;
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
_strCache = (string)value;
_dCache = double.Parse(_strCache);
return _dCache;
}
}
//below is codebehind usage:
Binding bd = new Binding(path);
bd.Converter = new double2txtConverter();
bd.Source = source;
bd.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
txtTarget.SetBinding(TextBox.TextProperty, bd);
I am giving answer based on the answer of Herm above. The explanation is correct, but using Delay on control won't solve the issue fully. If the end user types 0.005, the delay required will be more, otherwise it will re-write value as 0.
Instead, Use a string property for binding and try to parse it to double and based on parse output set the long value you need. Put all kind of validations you need before setting the value
private double _amount;
private string _amountString;
public string Amount
{
get { return _amountString;}
set {
double d=0;
if(Double.TryParse(value, out d))
{
_amountString=value;
_amount=d;
}
}
}
}
In binding of the property use, UpdateSourceTrigger=LostFocus. It will update the property once textbox is out of focus.
I resolved this by additonal property.
public class DoubleTextBox : TextBox
{
public string DoubleText
{
get => (string)GetValue(DoubleTextProperty);
set => SetValue(DoubleTextProperty, value);
}
public static readonly DependencyProperty DoubleTextProperty =
DependencyProperty.Register(
nameof(DoubleText),
typeof(string),
typeof(DoubleTextBox),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
new PropertyChangedCallback(OnDoubleTextChanged),
null,
true,
UpdateSourceTrigger.LostFocus));
private static void OnDoubleTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
var currentText = textBox.Text;
var newText = (string)e.NewValue;
if (currentText == newText)
return;
if (
double.TryParse(currentText, out var currentDouble) &&
double.TryParse(newText, out var newDouble) &&
currentDouble == newDouble
)
return;
textBox.Text = newText;
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
this.DoubleText = this.Text;
}
}
Usage:
<myControl:DoubleTextBox DoubleText="{Binding Double1, UpdateSourceTrigger=PropertyChanged}" />
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
I need to change border background color based of the textblock text string value from the binding. I planned to use triggers but it is not supported in Silverlight. I am looking for any advice on how it is achievable in Silverlight. Thank you in advance!
XAML:
<data:DataGridTemplateColumn Header="Y Position" Width="100">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Background="Red" Width="10" Height="18" VerticalAlignment="Center" Margin="0,0,10,0" />
<TextBlock Text="{Binding Y}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
ViewModel code:
public class MainPage_ViewModel : INotifyPropertyChanged
{
public
public MainPage_ViewModel()
{
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 1, Y = 2 }));
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 2, Y = 4 }));
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 3, Y = 6 }));
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 4, Y = 8 }));
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 5, Y = 10 }));
coordinates.Add(new Coordinate_DataViewModel(new Coordinate_Model() { X = 6, Y = 12 }));
}
public ObservableCollection<Coordinate_DataViewModel> Coordinates
{
get { return coordinates; }
set
{
if (coordinates != value)
{
coordinates = value;
OnPropertyChanged("Coordinates");
}
}
}
private ObservableCollection<Coordinate_DataViewModel> coordinates = new ObservableCollection<Coordinate_DataViewModel>();
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void DeleteCoordinate(Coordinate_DataViewModel dvmToDelete)
{
coordinates.Remove(dvmToDelete);
}
public void UpdateCoordinate(Coordinate_DataViewModel dvmToDelete)
{
}
}
//Model
public class Coordinate_Model
{
public double X;
public double Y;
}
//DataViewModel
public class Coordinate_DataViewModel
{
public Coordinate_DataViewModel(Coordinate_Model model)
{
this.underlyingModel = model;
}
private Coordinate_Model underlyingModel;
public double X
{
get { return underlyingModel.X; }
set { underlyingModel.X = value; }
}
public double Y
{
get { return underlyingModel.Y; }
set { underlyingModel.Y = value; }
} public string XYCoordinate
{
get { return "(" + X + "," + Y + ")"; }
}
}
I'm not really a fan of putting colors inside your viewmodel. In my opinion it is best to use a converter then like so:
public class CoordinateToColorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((int) value == 2 ? Colors.Red : Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
you can then define your binding like this:
<Border Background="{Binding Y, Converter={StaticResource CoordinateToColorConverter}}" Width="10" Height="18" VerticalAlignment="Center" Margin="0,0,10,0" />
I was having this discussion with someone else recently. I personally used the technique Luc Bos provided but there is another technique: RYODTS or Roll Your Own DataTemplateSelector.
This can be done with relative little effort. An example can be found on CodeProject.
if you're using an MVVM development model, then you would do something like:
<Border Background="{Binding BackgroundColor}" ...
...and create a Property in your ViewModel which provides the color brush you want. Then, all the decision logic about that border color can reside next to your data.
public double Y
{
get { return underlyingModel.Y; }
set { underlyingModel.Y = value;
// here, test underlyingModel.Y, and
// set backgroundColor private property, and
// raise the PropertyChanged event on "BackgroundColor"
}
}
private Brush backgroundColor;
public Brush BackgroundColor {
get { return backgroundColor; }
set { // whatever you want to do here, probably just
backgroundColor=value;
OnPropertyChanged("BackgroundColor")};
}
}
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))