WPF UserControl based on generic object properties - c#

I have an object based on byte data with over 200 properties that I care about, in the sense that I want to (1) know the value, and (2) know when the value changed from one message to the next.
A snippet of the XAML I am using:
<Label Content="Prop Name" />
<TextBlock Text="{Binding PropName}"
Background="{Binding PropName,
Converter={StaticResource CompareToLastValueConverter}}" />
Currently, I have these lines pasted for EACH property, with appropriate grid location settings.
My question is this: is there a nice way to create a nested WPF UserControl that takes a generic object property from the model and handles assigning the name (with spaces) to the Label, then assigning the value of the property to the TextBlock like the example above?
Also, is this the best way to think about this problem, or am I missing a link in the "WPF way" of doing things?

I've often wanted to try this. I'd create an ItemsControl template for PropertyInfo.
I created a test class:
public class MyClass
{
public string PropertyTest1 {get;set;}
public string PropertyTest2 { get; set; }
public string PropertyTest3 { get; set; }
public string PropertyTest4 { get; set; }
}
To display the properties of. In my data context for the display, I've got two things to bind to. A list of PropertyInfos, and the object in question. Since the PropertyInfo is static, you might be able to do this a better way using a converter or something, and not need to bind it to a property:
public PropertyInfo[] Properties
{
get { return typeof(MyClass).GetProperties(); }
}
public MyClass MyObject
{
get { return new MyClass { PropertyTest1 = "test", PropertyTest3 = "Some string", PropertyTest4 = "Last Property" }; }
}
Now, displaying the properties is easy:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.Resources>
<local:PropertyInfoValueConverter x:Key="PropertyInfoValueConverter"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
<TextBlock Grid.Column="1" Margin="4,2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But these are 'static', we can't bind to any values. A way to get around that is to use the Tag property, and a multi-binding converter:
So lets add Tag="{Binding MyObject}" to our ItemsSource, and throw that and the PropertyInfo into a value converter for our second textblock:
<TextBlock Grid.Column="1" Margin="4,2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyInfoValueConverter}">
<Binding Path=""/>
<Binding ElementName="PropertyDisplay" Path="Tag"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter is actually pretty simple, especially since you're not using text-boxes (so only going the read-only direction):
public class PropertyInfoValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PropertyInfo propertyInfo = values[0] as PropertyInfo;
return propertyInfo.GetValue(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the result:
You say you want spaces for the names, that could be done with a converter with some logic looking for whatever naming convention you've got (spaces before capital letters?).
It would be fun to play with template selectors to choose boolean, string, float templates and treat them differently. (Checkboxes, text, 00.00 formatted text etc)
Edit: Exploring Template Selector
Here's a sample template selector:
public class PropertyInfoTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DecimalTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = item as PropertyInfo;
if (propertyInfo.PropertyType == typeof(string))
{
return StringTemplate;
}
else if (propertyInfo.PropertyType == typeof(int))
{
return IntegerTemplate;
}
else if (propertyInfo.PropertyType == typeof(float) || propertyInfo.PropertyType == typeof(double))
{
return DecimalTemplate;
}
else if (propertyInfo.PropertyType == typeof(bool))
{
return BooleanTemplate;
}
return DefaultTemplate;
}
}
Our ItemsControl is now simply:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
Tag="{Binding MyObject}"
ItemTemplateSelector="{StaticResource PropertyInfoTemplateSelector}"
Margin="20"/>
I also added spaces in names using this converter:
public class PropertyInfoNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
(Credit to this: https://stackoverflow.com/a/272929/1305699).
Updating our class to contain some boolean and fload fields:

Related

DataBind a Dictionary value to ObservableCollection C# - XAML

I've got a Observable collection of custom objects and a public dictionary variable.
I would like the "BrandName" attribute to act as the Key for the "Brands" dictionary and bind the colour to the button. How would I go about doing this? The dictionary variable is outside of the class.
C# Code:
private ObservableCollection<BusService> BusServicesGUI;
public Dictionary<String, Brush> Brands;
public MainWindow(Dictionary<String, BusService> busServices)
{
InitializeComponent();
BusServicesGUI = new ObservableCollection<BusService>(BusServices.Values);
lstMachineFunctions.ItemsSource = BusServicesGUI;
lstMachineFunctions.Items.Refresh();
}
C# Class:
public class BusService
{
public string ServiceNumber { get; set; }
public string BrandName { get; set; }
public List<Location> Locations { get; set; }
public BusService(string brandName, string serviceNumber)
{
BrandName = brandName;
ServiceNumber = serviceNumber;
Locations = new List<Location>();
}
}
XAML CODE:
<StackPanel x:Name="ServiceStack">
<ItemsControl x:Name="lstMachineFunctions">
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<usercontrols:BusServiceCard/>
<Button Tag="{Binding ServiceNumber}" Background="{Binding Brands[BrandName]}" Height="50" Click="ButtonCl"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
As you can see from the XAML my current attempts have been trying Background="{Binding Brands[BrandName]}" This has however not worked, any help would be much appreciated.
You can use an IValueConverter to pefrom this operation.
public class BrandColorConverter : IValueConverter
{
public Dictionary<String, Brush> Brands = new Dictionary<string, Brush>()
{
{ "brand1", Brushes.Red },
{ "brand2", Brushes.Blue }
};
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!(value is BusService))
return Binding.DoNothing;
var busService = (BusService)value;
if (!Brands.ContainsKey(busService.BrandName))
return Binding.DoNothing;
return Brands[busService.BrandName];
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
In xaml, add it as a static resuorce:
<Window.Resources>
<local:BrandColorConverter x:Key="BrandColorConverter"/>
</Window.Resources>
And use it in your button:
<Button Tag="{Binding ServiceNumber}"
Background="{Binding Converter={StaticResource BrandColorConverter}}"
Height="50"
Click="ButtonCl"/>
This binding goes to the current element, so the whole BusService object will be passed to the converter.
Hope it solves your problem.
I would strongly advise you to look into MVVM pattern if you are going to use WPF with data binding, as it makes things much more streamlined.

Changing ListBox item depending on DateTime

I have a ListBox with some items bound to it. Those items are read from files like so:
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
bindList();
}
private void bindList()
{
var appStorage = IsolatedStorageFile.GetUserStoreForApplication();
string[] fileList = appStorage.GetFileNames();
List<Activity> activities = new List<Activity>();
foreach (string file in fileList)
{
string fileName = file;
string cYear = file.Substring(0, 4);
string cMonth = file.Substring(5, 2);
string cDay = file.Substring(8, 2);
string cHour = file.Substring(11, 2);
string cMinute = file.Substring(14, 2);
string cSeconds = file.Substring(17, 2);
DateTime dateCreated = new DateTime(int.Parse(cYear), int.Parse(cMonth), int.Parse(cDay), int.Parse(cHour), int.Parse(cMinute), int.Parse(cSeconds));
string dYear = file.Substring(20, 4);
string dMonth = file.Substring(25, 2);
string dDay = file.Substring(28, 2);
DateTime dateDeadline = new DateTime(int.Parse(dYear), int.Parse(dMonth), int.Parse(dDay));
string aTitle = file.Substring(31);
aTitle = aTitle.Substring(0, aTitle.Length - 4);
activities.Add(new Activity() { Title = aTitle, DateCreated = dateCreated.ToLongDateString(), Deadline = dateDeadline.ToLongDateString(), FileName = fileName });
}
activityListBox.ItemsSource = activities;
}
As you can see I'm reading dates and a title from the file name. Afterwards I bind them to the ListBox. What I want to do is change ListBox item (2 textbox and a hyperlink) color each time the dateDeadline is past the current date.
Here is how my ListBox looks like:
<ListBox HorizontalAlignment="Stretch"
Name="activityListBox"
VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<HyperlinkButton Name="activityTitle"
FontSize="40"
Content="{Binding Title}"
HorizontalContentAlignment="Left"
Tag="{Binding FileName}"
Click="activityTitle_Click"
/>
<TextBlock Name="activityDateCreated"
Text="{Binding DateCreated, StringFormat='Stworzono: {0}'}"
Margin="10" />
<TextBlock Name="activityDeadline"
Text="{Binding Deadline, StringFormat='Deadline: {0}'}"
Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Every guide I found was dealing with particular ListBox item (like changing 3rd item, 4th item etc.) and it does not solve my problem. I want to be able to check if the Deadline is past the current date each time files are loaded to the app and change it accordingly.
I'll greatly appreciate your help.
You can use a converter for just such a thing.
<UserControl.Resources>
<ResourceDictionary>
<local:DateToColorConverter x:Key="DateToColorConverter"/>
</ResourceDictionary>
</UserControl.Resources>
...
<TextBlock Name="activityDateCreated"
Text="{Binding DateCreated, StringFormat='Stworzono: {0}'}"
Margin="10"
Foreground="{Binding Deadline, Converter={StaticResource DateToColorConverter}" />
...
Your Converter (put this in your code behind)...
public class DateToColorConverter : IValueConverter
{
static SolidColorBrush _normalColor = new SolidColorBrush(Colors.Black);
static SolidColorBrush _pastDeadlineColor = new SolidColorBrush(Colors.Red);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime)
{
var deadline = value as DateTime;
return deadline < DateTime.Now ? _pastDeadlineColor : _normalColor;
}
return _normalColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
BTW - you should use an ObservableCollection instead of a List to hold your activity objects. Also, make sure your activity object supports INotifyPropertyChanged and that all your property methods call PropertyChanged.
In order to achieve this, you would first want to add a property Forecolor to your Activity class. This property will be a getter property that returns a color based on your condition (In this case, if the current date is greater than the deadline, return Red else Green). Note that I have changed your Deadline data type to Date to allow comparison of dates.
public DateTime Deadline { get; set; }
public Color Forecolor
{
get
{
if (DateTime.Now > Deadline)
return Colors.Red;
else
return Colors.Green;
}
}
Now bind you controls Foreground property to this property Forecolor
Foreground="{Binding Forecolor, Converter={StaticResource ColorToSolidColorBrush_ValueConverter}}"
Since the Foreground property expects a Brush, it will not work with just a color binding, you will need to use a converter that converts a Color to a Brush.
Define a Converter class in your project.
public class ColorToSolidColorBrushValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (null == value)
{
return null;
}
// For a more sophisticated converter, check also the targetType and react accordingly..
if (value is Color)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
// You can support here more source types if you wish
// For the example I throw an exception
Type type = value.GetType();
throw new InvalidOperationException("Unsupported type [" + type.Name + "]");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// If necessary, here you can convert back. Check if which brush it is (if its one),
// get its Color-value and return it.
throw new NotImplementedException();
}
}
Finally define the converter in your window resources.
<Window.Resources>
<local:ColorToSolidColorBrushValueConverter x:Key="ColorToSolidColorBrush_ValueConverter"/>
</Window.Resources>
Note: I have put in code from a WPF project. There may be a few syntax issues if your project is in WP7 (Though I think, it should work). However the principle is the same.

Bind string from .resx ResourceDictionary to TextBlock.Text using index key

i'm trying to get my view in different languages, using Properties/Resources.resx file for localization.
I've my model looks like the following:
class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
}
The ViewModel:
class ViewModel
{
public ObservableCollection<City> Cities { get; set; }
}
And on my View, i've the following code:
<ItemsControl ItemsSource="{Binding Cities}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocalizationKey}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When i want to get the value of my string key from the dictionary for only one item without Items Collection it works correctly by using the following code:
<TextBlock Text="{x:Static properties:Resources.MyStringKey}" />
The problem is when using the code above with an ItemsControl where the keys are unknowns! Is there any way to access to the dictionary values by using the LocalizationKey as an index?
Can you do something like:
public class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
public City(string englishName)
{
LocalizationKey = Properties.Resources.ResourceManager.GetString(englishName);
}
}
I'm not sure this best practice; but it what came to mind first.
After hours of web searching, i finally found a solution by using a converter, it may not the best practices to solve the problem but at least it does exactly what i want:
My Converter:
public class LocalizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value as string;
if (!string.IsNullOrEmpty(key))
{
string dictionaryValue = Resources.ResourceManager.GetString(key);
return dictionaryValue ?? key;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the XAML code:
<TextBlock Text="{Binding LocalizationId, Converter={StaticResource LocalizationConverter}}" />
Thanks.

How to data-bind to a property of a composite object in a collection?

I need to allow selection of several items from this predefined list:
public enum QuarkType{
Up,
Down,
[Description("Magical Being")] Charm,
[Description("Quite Odd")] Strange,
Top,
Bottom
}
So I use CheckComboBox, and use the DescriptionAttribute where I need to use custom description. I feed the CheckComboBox using a MarkupExtension that returns a list of all values of the given enum as IEnumerable<EnumDescriptionPair>, where EnumDescriptionPair is:
public class EnumDescriptionPair{
public object Value { get; set; }
public string Description { get; set; }
}
Now the problem is how to pass the Values of this list to the code-behind list:
public ObservableCollection<QuarkType> SelectedQuarksList { get; set; }
I mean, how to take just the Value out of the EnumDescriptionPair for each item of the selected list ?
This is what I have thus far. It obviously doesn't work (meaning it shows the right strings in the CheckComboBox, and allows selecting several items, but isn't reflected in the SelectedQuarksList mentioned above):
<Window x:Class="MyEditor.MainWindow"
xmlns:loc="clr-namespace:MyEditor"
xmlns:toolKit="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<toolKit:CheckComboBox x:Name="Ccb" Delimiter=","
ItemsSource="{loc:EnumItemsValueConverter {x:Type loc:QuarkType}}"
DisplayMemberPath="Description"
SelectedItemsOverride="{Binding SelectedQuarksList}" />
<ListBox ItemsSource="{Binding SelectedQuarksList}" />
</StackPanel>
</Window>
To do exactly what your question asks, you could try using a converter on the SelectedQuarksList binding that does a ".Select(q => q.Value)" in the ConvertBack function.
To get the behavior you want, I have done this successfully in the past (example with 2 of your values), this sets up the enum as "Flags" so the value sequence goes 0, 1, 2, 4...:
<StackPanel Orientation="Horizontal">
<Checkbox Content="Up" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Up}}"
<Checkbox Content="Magical Being" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Charm}}"
</StackPanel>
The converter looks like:
Quark _lastSeenValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark paramQuark = (Quark)parameter;
Quark currentQuark = (Quark)value;
_lastSeenValue = currentQuark;
return currentQuark.HasFlag(paramQuark);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark newQuark = _lastSeenValue;
Quark paramQuark = (Quark)parameter;
if ((bool)value)
{
newQuark |= paramQuark;
}
else
{
newQuark &= ~paramQuark;
}
_lastSeenValue = newQuark;
return newQuark;
}
This could be converted to add or remove from a list relatively easily, but I know the code above works.

WPF Binding a ListBox to an enum, displaying the Description Attribute

Is it possible to use the ObjectDataProvider method to bind a ListBox to an enum, and style it somehow to display the Description attriibute? If so how would one go about doing this...?
Yes, it is possible. This will do it. Say we have the enum
public enum MyEnum
{
[Description("MyEnum1 Description")]
MyEnum1,
[Description("MyEnum2 Description")]
MyEnum2,
[Description("MyEnum3 Description")]
MyEnum3
}
Then we can use the ObjectDataProvider as
xmlns:MyEnumerations="clr-namespace:MyEnumerations"
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="MyEnumValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="MyEnumerations:MyEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
And for the ListBox we set the ItemsSource to MyEnumValues and apply an ItemTemplate with a Converter.
<ListBox Name="c_myListBox" SelectedIndex="0" Margin="8"
ItemsSource="{Binding Source={StaticResource MyEnumValues}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in the converter we get the description and return it
public class EnumDescriptionConverter : IValueConverter
{
private string GetEnumDescription(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;
}
}
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Enum myEnum = (Enum)value;
string description = GetEnumDescription(myEnum);
return description;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Empty;
}
}
The GetEnumDescription method should probably go somewhere else but you get the idea :)
Check GetEnumDescription as extension method.
Another solution would be a custom MarkupExtension that generates the items from enum type. This makes the xaml more compact and readable.
using System.ComponentModel;
namespace EnumDemo
{
public enum Numbers
{
[Description("1")]
One,
[Description("2")]
Two,
Three,
}
}
Example of usage:
<Window x:Class="EnumDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EnumDemo">
<ListBox ItemsSource="{local:EnumToCollection EnumType={x:Type local:Numbers}}"/>
</Window>
MarkupExtension implementation
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Markup;
namespace EnumDemo
{
public class EnumToCollectionExtension : MarkupExtension
{
public Type EnumType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (EnumType == null) throw new ArgumentNullException(nameof(EnumType));
return Enum.GetValues(EnumType).Cast<Enum>().Select(EnumToDescriptionOrString);
}
private string EnumToDescriptionOrString(Enum value)
{
return value.GetType().GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault()?.Description ?? value.ToString();
}
}
}
If you bind to the Enum, you could probably convert this to the description through an IValueConverter.
See Binding ComboBoxes to enums... in Silverlight! for a description on how to accomplish this.
See http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for more information.
You can define a ressource file in your project (*.resx file). In this file you must define "key-value-pairs", something like this:
"YellowCars" : "Yellow Cars",
"RedCars" : "Red Cars",
and so on...
The keys are equals to your enum-entries, something like this:
public enum CarColors
{
YellowCars,
RedCars
}
and so on...
When you use WPF you can implement in your XAML-Code, something like this:
<ComboBox ItemsSource="{Binding Source={StaticResource CarColors}}" SelectedValue="{Binding CarColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource CarColorConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Then you must write your Converter, something like this:
using System;
using System.Globalization;
using System.Resources;
using System.Windows.Data;
public class CarColorConverter : IValueConverter
{
private static ResourceManager CarColors = new ResourceManager(typeof(Properties.CarColors));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = ((Enum)value).ToString();
var result = CarColors.GetString(key);
if (result == null) {
result = key;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My answer comes 7 years to late ;-) But maybe it can be used by someone else!
Yeah, possible.
ListBox can help us do that, without converters.
The steps of this method are below:
create a ListBox and set the ItemsSource for the listbox as the enum and binding the SelectedItem of the ListBox to the selected property.
Then each ListBoxItem will be created.
Step 1: define your Enum.
public enum EnumValueNames
{
EnumValueName1,
EnumValueName2,
EnumValueName3
}
Then add below property to your DataContext (or ViewModel of MVVM), which records the selected item which is checked.
public EnumValueNames SelectedEnumValueName { get; set; }
Step 2: 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="EnumValueNames">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:EnumValueNames" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Step 3: Use the List Box to populate each item
<ListBox ItemsSource="{Binding Source={StaticResource EnumValueNames}}"
SelectedItem="{Binding SelectedEnumValueName, Mode=TwoWay}" />
References:
https://www.codeproject.com/Articles/130137/Binding-TextBlock-ListBox-RadioButtons-to-Enums
The example here is applied to a ComboBox, but will work all the same for any Enum Binding.
Origin:
This anwser is based on the original work of Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter.
I have made modifications for it to better suit my needs.
What I changed:
Extended the Enum class with a boolean function that checks if the EnumValue has the [Description] attribute or not
public static bool HasDescriptionAttribute(this Enum value)
{
var attribute = value.GetType().GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault();
return (attribute != null);
}
Modified Brian's "ConvertTo()" function in "EnumDescriptionTypeConverter" to return "null" in case the [Description] attribute was not applied
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
...
// Original: return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : null;
}
Modified Brian's "ProvideValue()" function in "EnumBindingSourceExtension" by editing it's return value by calling my own function
ProvideValue(IServiceProvider serviceProvider)
{
...
// Original: return enumValues
return SortEnumValuesByIndex(enumValues);
...
// Original: return tempArray
return SortEnumValuesByIndex(tempArray);
}
And adding my function to Sort the enum by Index (Original code had problems when going across Projects ..), and Strip out any Values that don't have the [Description] attribute:
private object SortEnumValuesByIndex(Array enumValues)
{
var values = enumValues.Cast<Enum>().ToList();
var indexed = new Dictionary<int, Enum>();
foreach (var value in values)
{
int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
indexed.Add(index, value);
}
return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast<Enum>();
}
This example has been applied to ComboBoxes:
Note: Failures in Uploading images to the Server, so i added a URL to the images in question
<ComboBox x:Name="ConversionPreset_ComboBox" Grid.Row="4" Grid.Column="1" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:ConversionPreset}}" SelectedIndex="2" SelectionChanged="ConversionPreset_ComboBox_SelectionChanged" />
<ComboBox x:Name="OutputType_ComboBox" Grid.Row="4" Grid.Column="2" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:Output}}" SelectedIndex="1" SelectionChanged="OutputType_ComboBox_SelectionChanged" />
With code behind:
private Enumeration.Output Output { get; set; }
private void OutputType_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> Output = (Enumeration.Output)OutputType_ComboBox.SelectedItem;
private Enumeration.ConversionPreset ConversionPreset { get; set; }
private void ConversionPreset_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> ConversionPreset = (Enumeration.ConversionPreset)ConversionPreset_ComboBox.SelectedItem;
The "ConversionPreset" Enum and a Picture of the ComboBox Rendered
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ConversionPreset
{
[Description("Very Slow (Smaller File Size)")]
VerySlow = -2,
[Description("Slow (Smaller File Size)")]
Slow = -1,
[Description("Medium (Balanced File Size)")]
Medium = 0,
[Description("Fast (Bigger File Size)")]
Fast = 1,
[Description("Very Fast (Bigger File Size)")]
VeryFast = 2,
[Description("Ultra Fast (Biggest File Size)")]
UltraFast = 3
}
The "Output" Enum and a Picture of the ComboBox Rendered
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Output
{
// This will be hidden in the Output
None = -1,
[Description("Video")]
Video = 0,
[Description("Audio")]
Audio = 1
}

Categories

Resources