Want to display "Yes"/"No" in a dynamically generated column for Boolean value instead of checkbox in RadGridView for WPF - wpf

I would like to display "Yes" or "No" for whenever a Boolean type data received(it can receive different types of data) for generating a column in RadGridView instead of a checkbox. I would like to implement this changes in xaml. Columns are generating dynamically. This is how it's created now:
<telerik:RadGridView x:Name="Data" Grid.Row="3" Margin="5" AutoGenerateColumns="False" CanUserSortColumns="True" IsFilteringAllowed="True"
grid:RadGridViewColumnsBinding.ColumnsCollection="{Binding Path=ColumnsData}"
IsReadOnly="False" CanUserResizeColumns="True"/>
I am new in Silverlight coding. Will really appreciate if someone can help.

You should check out Telerik's ConditionalDataTemplateSelector they have in this demo, and read about IValueConverter if you haven't already.
Depending on what you are trying to do with all your columns, the ConditionalDataTemplateSelector might be overkill, but you can use it to create a rule system for what DataTemplate to use for a given cell based on a custom rule system.
<Grid.Resources>
...
<DataTemplate x:Key="CellDisplayTextBox">
<TextBlock Text="{Binding Value, Converter={StaticResource BooleanToYesNoConverter}}" />
</DataTemplate>
<selector:ConditionalDataTemplateSelector x:Key="displaySelector" ConditionConverter="{StaticResource someConverter}">
<selector:ConditionalDataTemplateSelector.Rules>
<selector:ConditionalDataTemplateRule DataTemplate="{StaticResource CellDisplayTextBox}">
<selector:ConditionalDataTemplateRule.Value>
<sys:Int32>1</sys:Int32> <!--You need to figure out what value and type to use here -->
</selector:ConditionalDataTemplateRule.Value>
</selector:ConditionalDataTemplateRule>
...
</selector:ConditionalDataTemplateSelector.Rules>
</Grid.Resources>
...
<telerikGridView:RadGridView>
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn CellTemplateSelector="{StaticResource displaySelector}" CellEditTemplateSelector="{StaticResource editSelector}" />
</telerik:RadGridView.Columns>
</telerikGridView:RadGridView>
The IValueConverter will let you bind a bool value, but display a string value. For a BooleanToYesNoConverter you could do something like:
public class BooleanToYesNoConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? bValue = value as bool?;
if (bValue.HasValue)
return bValue.Value ? "Yes" : "No";
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string sValue = value as string;
return sValue == "Yes";
}
}
The ConditionalDataTemplateSelector code from the demo:
public class ConditionalDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
object conditionValue = this.ConditionConverter.Convert(item, null, null, null);
foreach (ConditionalDataTemplateRule rule in this.Rules)
{
if (Equals(rule.Value, conditionValue))
{
return rule.DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
List<ConditionalDataTemplateRule> _Rules;
public List<ConditionalDataTemplateRule> Rules
{
get
{
if (this._Rules == null)
{
this._Rules = new List<ConditionalDataTemplateRule>();
}
return this._Rules;
}
}
IValueConverter _ConditionConverter;
public IValueConverter ConditionConverter
{
get
{
return this._ConditionConverter;
}
set
{
this._ConditionConverter = value;
}
}
}
public class ConditionalDataTemplateRule
{
object _Value;
public object Value
{
get
{
return this._Value;
}
set
{
this._Value = value;
}
}
DataTemplate _DataTemplate;
public DataTemplate DataTemplate
{
get
{
return this._DataTemplate;
}
set
{
this._DataTemplate = value;
}
}
}

Related

WPF Pull Enum Description if available for populating ComboBox

I have an enum where some of the items have a DescriptionAttribute set.
I want to in my WPF app have a dropdown where the user can select an items from the enum, but I want the dropdown to utilize the Description value if it's available.
I wrote code to get the list of values (pull the description if available, else use name), and I have the XAML I'm trying to use for the Object Provider, but it doesn't populate anything.
The XAML works if I use GetValues with ObjectType Definition.
C#
public static string[] GetDescriptions(Enum enumType)
{
List<string> descriptions = new List<string>();
Type t = enumType.GetType();
foreach(string name in Enum.GetNames(t))
{
FieldInfo field = t.GetField(name);
object[] d = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (d.Any())
{
descriptions.Add(((DescriptionAttribute)d[0]).Description);
}
else
{
descriptions.Add(name);
}
}
return descriptions.ToArray();
}
XAML:
<ObjectDataProvider x:Key="SkillEnum" MethodName="KwCommon:EnumExtensions.GetDescriptions" >
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:SkillLevels"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
I do something similar to bind an enum to a combobox. I have a couple of helper functions:
public static string GetEnumDescription<TEnum>(this TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
return attributes[0].Description;
}
else
{
return value.ToString();
}
}
and this function that turns the enum into a list of keyvaluepairs:
public static IEnumerable<KeyValuePair<string, Enum>> GetEnumList(this Enum t, bool useDescription = true)
{
return Enum.GetValues(t.GetType()).Cast<Enum>().Select(e => new KeyValuePair<string, Enum>(useDescription == true ? e.GetEnumDescription() : e.ToString(), e)).ToList();
}
I create a converter (In my case markupextension, here is a link for that markupextension)
public class EnumToListConverter : ConverterMarkupExtension<EnumToListConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool useDescription = true;
if (parameter is bool p)
{
useDescription = p;
}
if (value is Enum e)
{
return EnumHelper.GetEnumList(e, useDescription);
}
return DependencyProperty.UnsetValue;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then in my xaml I use it like this:
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Applicant.RentOrOwn, Converter={local:EnumToListConverter}, Mode=OneTime}" SelectedValuePath="Value" DisplayMemberPath="Key" SelectedValue="{Binding Applicant.RentOrOwn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True}"></ComboBox>
In this example I have an Applicant class which has a RentOrOwn enum property. This will show all the values available in the enum, and then update the RentOrOwn property when the user clicks the new selected value in an MVVM style.

Text color depends of value

First of all, I am new to WPF and Xaml, so I just hope that you understand what I am asking.
I got this situation: There is a listBox of Animals. Every Animal has Weight property. What I am trying to achieve is whenever Weight of Animal is greater then 300 kg, that Weight should be displayed red.
You could use custom converter to achieve that. If your item looks like that:
public class Animal
{
public int Weight { get; set; }
public string Name { get; set; }
}
and ItemTemplate like that:
<DataTemplate x:Key="AnimalTemplate">
<TextBlock Text="{Binding Name}" Foreground="{Binding Weight, Converter={StaticResource AnimalColorSelector}}"/>
</DataTemplate>
Your converter will be like the following one:
public class AnimalColorSelector : IValueConverter
{
private readonly Color _overweightColor = Colors.Red;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
return (int) value > 300 ? new SolidColorBrush(_overweightColor) : Binding.DoNothing;
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
This approach has the following pros:
You don't need to hardcode the default color, but inherit it by using Binding.DoNothing.
You don't need to store any style information in a view model.
You could create a ViewModel for Animals that would contain necessary logic for color setting. Like this:
public class VMAnimal : INotifyPropertyChanged
{
private int _weight;
public int Weight
{
get { return _weight; }
set
{
_weight = value;
RaisePropertyChanged("Weight");
RaisePropertyChanged("Color");
}
}
public Brush Foreground
{
get
{
if (Weight > 300)
return new SolidColorBrush(Color.Red);
return new SolidColorBrush(Color.Black);
}
}
}
And use it with binding like this:
<TextBlock Text="{Binding Weight}" Foreground="{Binding Foreground}" />

Unexpected red border (validation error) on DataGrid when selecting blank row

When I select (by clicking or by keyboard) blank row on my DataGrid (when I want to add new row), unexpected validation error occurs (but with no exception) - the border of datagrid changes to red color, as you can see on the image below. When I click second time on blank row, the red border dissapears. Everything other works fine, the new row is added. Besides, I don't have any validation rules. And when I make a row with empty text, value is valid.
I don't want this behavior and this red border, anybody knows, why this happens and how to fix it? Why and where some validation fails?
Below I append some source code:
DataGrid definition in xaml:
<DataGrid IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"
ItemsSource="{Binding Path=ConfigFiles}" SelectedItem="{Binding Path=SelectedConfigFile}"
Grid.Column="1" Height="87" Margin="0,26,11,32" Style="{DynamicResource DataGridStyle}">
<DataGrid.Columns>
<DataGridTextColumn Width="1*" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>
My ViewModel's part:
public class ManageModulesVM : BaseVM // Implements INotifyPropertyChanged
{
// ...
public ObservableCollection<ConfigFile> ConfigFiles
{
get { return selectedModule == null ? null : selectedModule.ConfigFiles; }
set
{
selectedModule.ConfigFiles = value;
OnPropertyChanged(() => ConfigFiles);
}
}
public ConfigFile SelectedConfigFile
{
get { return selectedModule == null ? null : selectedModule.SelectedConfigFile; }
set
{
if (value != null)
{
selectedModule.SelectedConfigFile = value;
}
OnPropertyChanged(() => SelectedConfigFile);
OnPropertyChanged(() => Parameters);
}
}
// ...
}
ConfigFile class:
public class ConfigFile
{
public string Name { get; set; }
public IList<Parameter> Parameters { get; set; }
public ConfigFile() { Name = ""; Parameters = new List<Parameter>(); }
}
Edit:
After further investigation I know, that SelectedItem Binding is causing problems (when I remove this binding, validation error stops to appear), but I still don't know why and how to fix this.
I've found my own solution to this question. I've written a value converter and tied it to the binding:
(SelectedItem="{Binding Path=SelectedConfigFile,Converter={StaticResource configFileConverter}}")
The converter class:
namespace Converters
{
public class SelectedConfigFileConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is ConfigFile)
return value;
return null;
}
}
}
Define resource in resources.xaml file (or in any other resources place):
<ResourceDictionary (...) xmlns:conv="clr-namespace:Converters" >
<conv:SelectedConfigFileConverter x:Key="configFileConverter" />
</ResourceDictionary>
The advantage of this solution is that the SelectedConfigFile property's type did't changed (to the general object type) so it is still strongly typed.
To get the reason, when you click the new row of DataGrid in Debug mode, please see the debug window. There are first exception messages which will give you the idea why your problem is occurred.
Yes, the problem is from type casting. You need to modify the type of SelectedItem to object type as below.
public class ManageModulesVM : BaseVM // Implements INotifyPropertyChanged
{
// ...
public object SelectedConfigFile
{
get { return selectedModule == null ? null : selectedModule.SelectedConfigFile; }
set
{
if (value != null)
{
selectedModule.SelectedConfigFile = value;
}
OnPropertyChanged(() => SelectedConfigFile);
OnPropertyChanged(() => Parameters);
}
}
// ...
}
Here's a general-purpose converter you can use for any DataGrid, binding any kind of item:
public class DataGridItemConverter : MarkupExtension, IValueConverter
{
static DataGridItemConverter converter;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value != null && value.GetType() == targetType) ? value : null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (converter == null)
converter = new DataGridItemConverter();
return converter;
}
}
Since it implements MarkupExtension you don't even need to define a static resource, you can just reference it like this:
SelectedItem="{Binding SelectedThing,Converter={conv:DataGridItemConverter}}"
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
It will solve the problem

Example Using Radio Buttons on Form [duplicate]

I've got an enum like this:
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
I got a property in my DataContext:
public MyLovelyEnum VeryLovelyEnum { get; set; }
And I got three RadioButtons in my WPF client.
<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>
Now how do I bind the RadioButtons to the property for a proper two-way binding?
You can further simplify the accepted answer. Instead of typing out the enums as strings in xaml and doing more work in your converter than needed, you can explicitly pass in the enum value instead of a string representation, and as CrimsonX commented, errors get thrown at compile time rather than runtime:
ConverterParameter={x:Static local:YourEnumType.Enum1}
<StackPanel>
<StackPanel.Resources>
<local:ComparisonConverter x:Key="ComparisonConverter" />
</StackPanel.Resources>
<RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static local:YourEnumType.Enum1}}" />
<RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static local:YourEnumType.Enum2}}" />
</StackPanel>
Then simplify the converter:
public class ComparisonConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value?.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value?.Equals(true) == true ? parameter : Binding.DoNothing;
}
}
Edit (Dec 16 '10):
Thanks to anon for suggesting returning Binding.DoNothing rather than DependencyProperty.UnsetValue.
Note - Multiple groups of RadioButtons in same container (Feb 17 '11):
In xaml, if radio buttons share the same parent container, then selecting one will de-select all other's within that container (even if they are bound to a different property). So try to keep your RadioButton's that are bound to a common property grouped together in their own container like a stack panel. In cases where your related RadioButtons cannot share a single parent container, then set the GroupName property of each RadioButton to a common value to logically group them.
Edit (Apr 5 '11):
Simplified ConvertBack's if-else to use a Ternary Operator.
Note - Enum type nested in a class (Apr 28 '11):
If your enum type is nested in a class (rather than directly in the namespace), you might be able to use the '+' syntax to access the enum in XAML as stated in a (not marked) answer to the question :
ConverterParameter={x:Static local:YourClass+YourNestedEnumType.Enum1}
Due to this Microsoft Connect Issue, however, the designer in VS2010 will no longer load stating "Type 'local:YourClass+YourNestedEnumType' was not found.", but the project does compile and run successfully. Of course, you can avoid this issue if you are able to move your enum type to the namespace directly.
Edit (Jan 27 '12):
If using Enum flags, the converter would be as follows:
public class EnumToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((Enum)value).HasFlag((Enum)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
Edit (May 7 '15):
In case of a Nullable Enum (that is **not** asked in the question, but can be needed in some cases, e.g. ORM returning null from DB or whenever it might make sense that in the program logic the value is not provided), remember to add an initial null check in the Convert Method and return the appropriate bool value, that is typically false (if you don't want any radio button selected), like below:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) {
return false; // or return parameter.Equals(YourEnumType.SomeDefaultValue);
}
return value.Equals(parameter);
}
Note - NullReferenceException (Oct 10 '18):
Updated the example to remove the possibility of throwing a NullReferenceException. `IsChecked` is a nullable type so returning `Nullable` seems a reasonable solution.
You could use a more generic converter
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
And in the XAML-Part you use:
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
</StackPanel>
</Grid>
For the EnumToBooleanConverter answer:
Instead of returning DependencyProperty.UnsetValue consider returning Binding.DoNothing for the case where the radio button IsChecked value becomes false.
The former indicates a problem (and might show the user a red rectangle or similar validation indicators) while the latter just indicates that nothing should be done, which is what is wanted in that case.
http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.convertback.aspx
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.donothing.aspx
I would use the RadioButtons in a ListBox, and then bind to the SelectedValue.
This is an older thread about this topic, but the base idea should be the same: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/
For UWP, it is not so simple: You must jump through an extra hoop to pass a field value as a parameter.
Example 1
Valid for both WPF and UWP.
<MyControl>
<MyControl.MyProperty>
<Binding Converter="{StaticResource EnumToBooleanConverter}" Path="AnotherProperty">
<Binding.ConverterParameter>
<MyLibrary:MyEnum>Field</MyLibrary:MyEnum>
</Binding.ConverterParameter>
</MyControl>
</MyControl.MyProperty>
</MyControl>
Example 2
Valid for both WPF and UWP.
...
<MyLibrary:MyEnum x:Key="MyEnumField">Field</MyLibrary:MyEnum>
...
<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={StaticResource MyEnumField}}"/>
Example 3
Valid only for WPF!
<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static MyLibrary:MyEnum.Field}}"/>
UWP doesn't support x:Static so Example 3 is out of the question; assuming you go with Example 1, the result is more verbose code. Example 2 is slightly better, but still not ideal.
Solution
public abstract class EnumToBooleanConverter<TEnum> : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var Parameter = parameter as string;
if (Parameter == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(typeof(TEnum), value) == false)
return DependencyProperty.UnsetValue;
return Enum.Parse(typeof(TEnum), Parameter).Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var Parameter = parameter as string;
return Parameter == null ? DependencyProperty.UnsetValue : Enum.Parse(typeof(TEnum), Parameter);
}
}
Then, for each type you wish to support, define a converter that boxes the enum type.
public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
//Nothing to do!
}
The reason it must be boxed is because there's seemingly no way to reference the type in the ConvertBack method; the boxing takes care of that. If you go with either of the first two examples, you can just reference the parameter type, eliminating the need to inherit from a boxed class; if you wish to do it all in one line and with least verbosity possible, the latter solution is ideal.
Usage resembles Example 2, but is, in fact, less verbose.
<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter=Field}"/>
The downside is you must define a converter for each type you wish to support.
I've created a new class to handle binding RadioButtons and CheckBoxes to enums. It works for flagged enums (with multiple checkbox selections) and non-flagged enums for single-selection checkboxes or radio buttons. It also requires no ValueConverters at all.
This might look more complicated at first, however, once you copy this class into your project, it's done. It's generic so it can easily be reused for any enum.
public class EnumSelection<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
private T value; // stored value of the Enum
private bool isFlagged; // Enum uses flags?
private bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can)
private T blankValue; // what is considered the "blank" value if it can be deselected?
public EnumSelection(T value) : this(value, false, default(T)) { }
public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default(T)) { }
public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { }
public EnumSelection(T value, bool canDeselect, T blankValue)
{
if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums...
isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false);
this.value = value;
this.canDeselect = canDeselect;
this.blankValue = blankValue;
}
public T Value
{
get { return value; }
set
{
if (this.value.Equals(value)) return;
this.value = value;
OnPropertyChanged();
OnPropertyChanged("Item[]"); // Notify that the indexer property has changed
}
}
[IndexerName("Item")]
public bool this[T key]
{
get
{
int iKey = (int)(object)key;
return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key);
}
set
{
if (isFlagged)
{
int iValue = (int)(object)this.value;
int iKey = (int)(object)key;
if (((iValue & iKey) == iKey) == value) return;
if (value)
Value = (T)(object)(iValue | iKey);
else
Value = (T)(object)(iValue & ~iKey);
}
else
{
if (this.value.Equals(key) == value) return;
if (!value && !canDeselect) return;
Value = value ? key : blankValue;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And for how to use it, let's say you have an enum for running a task manually or automatically, and can be scheduled for any days of the week, and some optional options...
public enum StartTask
{
Manual,
Automatic
}
[Flags()]
public enum DayOfWeek
{
Sunday = 1 << 0,
Monday = 1 << 1,
Tuesday = 1 << 2,
Wednesday = 1 << 3,
Thursday = 1 << 4,
Friday = 1 << 5,
Saturday = 1 << 6
}
public enum AdditionalOptions
{
None = 0,
OptionA,
OptionB
}
Now, here's how easy it is to use this class:
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
StartUp = new EnumSelection<StartTask>(StartTask.Manual);
Days = new EnumSelection<DayOfWeek>(default(DayOfWeek));
Options = new EnumSelection<AdditionalOptions>(AdditionalOptions.None, true, AdditionalOptions.None);
}
public EnumSelection<StartTask> StartUp { get; private set; }
public EnumSelection<DayOfWeek> Days { get; private set; }
public EnumSelection<AdditionalOptions> Options { get; private set; }
}
And here's how easy it is to bind checkboxes and radio buttons with this class:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<!-- Using RadioButtons for exactly 1 selection behavior -->
<RadioButton IsChecked="{Binding StartUp[Manual]}">Manual</RadioButton>
<RadioButton IsChecked="{Binding StartUp[Automatic]}">Automatic</RadioButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<!-- Using CheckBoxes for 0 or Many selection behavior -->
<CheckBox IsChecked="{Binding Days[Sunday]}">Sunday</CheckBox>
<CheckBox IsChecked="{Binding Days[Monday]}">Monday</CheckBox>
<CheckBox IsChecked="{Binding Days[Tuesday]}">Tuesday</CheckBox>
<CheckBox IsChecked="{Binding Days[Wednesday]}">Wednesday</CheckBox>
<CheckBox IsChecked="{Binding Days[Thursday]}">Thursday</CheckBox>
<CheckBox IsChecked="{Binding Days[Friday]}">Friday</CheckBox>
<CheckBox IsChecked="{Binding Days[Saturday]}">Saturday</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<!-- Using CheckBoxes for 0 or 1 selection behavior -->
<CheckBox IsChecked="{Binding Options[OptionA]}">Option A</CheckBox>
<CheckBox IsChecked="{Binding Options[OptionB]}">Option B</CheckBox>
</StackPanel>
</StackPanel>
When the UI loads, the "Manual" radio button will be selected and you can alter your selection between "Manual" or "Automatic" but either one of them must always be selected.
Every day of the week will be unchecked, but any number of them can be checked or unchecked.
"Option A" and "Option B" will both initially be unchecked. You can check one or the other, checking one will uncheck the other (similar to RadioButtons), but now you can also uncheck both of them (which you cannot do with WPF's RadioButton, which is why CheckBox is being used here)
This work for Checkbox too.
public class EnumToBoolConverter:IValueConverter
{
private int val;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int intParam = (int)parameter;
val = (int)value;
return ((intParam & val) != 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
val ^= (int)parameter;
return Enum.Parse(targetType, val.ToString());
}
}
Binding a single enum to multiple checkboxes.
You can create the radio buttons dynamically, ListBox can help you do that, without converters, quite simple.
The concrete steps are below:
create a ListBox and set the ItemsSource for the listbox as the enum MyLovelyEnum and binding the SelectedItem of the ListBox to the VeryLovelyEnum property.
then the Radio Buttons for each ListBoxItem will be created.
Step 1: add the enum to static resources for your Window, UserControl or Grid etc.
<Window.Resources>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type system:Enum}"
x:Key="MyLovelyEnum">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:MyLovelyEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Step 2: Use the List Box and Control Template to populate each item inside as Radio button
<ListBox ItemsSource="{Binding Source={StaticResource MyLovelyEnum}}" SelectedItem="{Binding VeryLovelyEnum, Mode=TwoWay}" >
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
The advantage is: if someday your enum class changes, you do not need to update the GUI (XAML file).
References:
https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/
One way to handle this would be to have separate bool properties in your ViewModel class. Here is how I've handled with such a situation:
ViewModel:
public enum MyLovelyEnum { FirstSelection, TheOtherSelection, YetAnotherOne };
private MyLovelyEnum CurrentSelection;
public bool FirstSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.FirstSelection;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.FirstSelection;
}
}
public bool TheOtherSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.TheOtherSelection;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.TheOtherSelection;
}
}
public bool YetAnotherOneSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.YetAnotherOne;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.YetAnotherOne;
}
}
XAML:
<RadioButton IsChecked="{Binding SimilaritySort, Mode=TwoWay}">Similarity</RadioButton>
<RadioButton IsChecked="{Binding DateInsertedSort, Mode=TwoWay}">Date Inserted</RadioButton>
<RadioButton IsChecked="{Binding DateOfQuestionSort, Mode=TwoWay}">Date of Question</RadioButton>
<RadioButton IsChecked="{Binding DateModifiedSort, Mode=TwoWay}">Date Modified</RadioButton>
It's not as robust or dynamic as some of the other solutions, but the nice thing is it's very self-contained and doesn't require creating custom converters or anything like that.
Based on the EnumToBooleanConverter from Scott.
I noticed that the ConvertBack method doesn't work on the Enum with flags code.
I've tried the following code:
public class EnumHasFlagToBooleanConverter : IValueConverter
{
private object _obj;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
_obj = value;
return ((Enum)value).HasFlag((Enum)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.Equals(true))
{
if (((Enum)_obj).HasFlag((Enum)parameter))
{
// Do nothing
return Binding.DoNothing;
}
else
{
int i = (int)_obj;
int ii = (int)parameter;
int newInt = i+ii;
return (NavigationProjectDates)newInt;
}
}
else
{
if (((Enum)_obj).HasFlag((Enum)parameter))
{
int i = (int)_obj;
int ii = (int)parameter;
int newInt = i-ii;
return (NavigationProjectDates)newInt;
}
else
{
// do nothing
return Binding.DoNothing;
}
}
}
}
The only thing that I can't get to work is to do a cast from int to targetType so I made it hardcoded to NavigationProjectDates, the enum that I use. And, targetType == NavigationProjectDates...
Edit for more generic Flags Enum converter:
public class FlagsEnumToBooleanConverter : IValueConverter {
private int _flags=0;
public object Convert(object value, Type targetType, object parameter, string language) {
if (value == null) return false;
_flags = (int) value;
Type t = value.GetType();
object o = Enum.ToObject(t, parameter);
return ((Enum)value).HasFlag((Enum)o);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value?.Equals(true) ?? false) {
_flags = _flags | (int) parameter;
}
else {
_flags = _flags & ~(int) parameter;
}
return _flags;
}
}
A TwoWay Binding solution to UWP that takes the usage of Nullable:
C# Part:
public class EnumConverter : IValueConverter
{
public Type EnumType { get; set; }
public object Convert(object value, Type targetType, object parameter, string lang)
{
if (parameter is string enumString)
{
if (!Enum.IsDefined(EnumType, value)) throw new ArgumentException("value must be an Enum!");
var enumValue = Enum.Parse(EnumType, enumString);
return enumValue.Equals(value);
}
return value.Equals(Enum.ToObject(EnumType,parameter));
}
public object ConvertBack(object value, Type targetType, object parameter, string lang)
{
if (parameter is string enumString)
return value?.Equals(true) == true ? Enum.Parse(EnumType, enumString) : null;
return value?.Equals(true) == true ? Enum.ToObject(EnumType, parameter) : null;
}
}
Here the null value acts as the Binding.DoNothing.
private YourEnum? _yourEnum = YourEnum.YourDefaultValue; //put a default value here
public YourEnum? YourProperty
{
get => _yourEnum;
set{
if (value == null) return;
_yourEnum = value;
}
}
Xaml Part:
...
<Page.Resources>
<ResourceDictionary>
<helper:EnumConverter x:Key="YourConverter" EnumType="yournamespace:YourEnum" />
</ResourceDictionary>
</Page.Resources>
...
<RadioButton GroupName="YourGroupName" IsChecked="{Binding Converter={StaticResource YourConverter}, Mode=TwoWay, Path=YourProperty, ConverterParameter=YourEnumString}">
First way (parameter of type string)
</RadioButton>
<RadioButton GroupName="LineWidth">
<RadioButton.IsChecked>
<Binding
Converter="{StaticResource PenWidthConverter}"
Mode="TwoWay" Path="PenWidth">
<Binding.ConverterParameter>
<yournamespace:YourEnum>YourEnumString</yournamespace:YourEnum>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
Second way (parameter of type YourEnum (actually it was converted to int when passed to converter))
</RadioButton>

WPF binding ComboBox to enum (with a twist)

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

Resources