How to dynamically bind enum value from collection?(WPF) [duplicate] - c#

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
}

Related

WPF: Bind TabControl SelectedIndex to a View Model's Enum Property

I have a property on my ViewModel that is an enum:
ViewModel:
public MyViewModel {
// Assume this is a DependancyProperty
public AvailableTabs SelectedTab { get; set; }
// Other bound properties
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
}
public enum AvailableTabs {
Tab1,
Tab2,
Tab3
}
I'd like to be able to bind SelectedIndex (or SelectedItem) of my TabControl to this property and have it correctly set the appropriate tab using a converter. Unfortunately, I'm a bit stuck. I know I can easily just use the SelectedIndex in my model, but I want the flexibility of re-ordering the tabs without breaking anything. I've given each TabItem a Tag property of the applicable enum value.
My XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab, Converter={StaticResource SomeConverter}}">
<TabItem Header="Tab 1" Tag="{x:Static local:AvailableTabs.Tab1}">
<TextBlock Text="{Binding Property1}" />
</TabItem>
<TabItem Header="Tab 2" Tag="{x:Static local:AvailableTabs.Tab2}">
<TextBlock Text="{Binding Property2}" />
</TabItem>
<TabItem Header="Tab 3" Tag="{x:Static local:AvailableTabs.Tab3}">
<TextBlock Text="{Binding Property3}" />
</TabItem>
</TabControl>
My problem is that I can't figure out how to get the TabControl into my converter so I can do:
// Set the SelectedIndex via the enum (Convert)
var selectedIndex = MyTabControl.Items.IndexOf(MyTabControl.Items.OfType<TabItem>().Single(t => (AvailableTabs) t.Tag == enumValue));
// Get the enum from the SelectedIndex (ConvertBack)
var enumValue = (AvailableTabs)((TabItem)MyTabControl.Items[selectedIndex]).Tag;
I'm afraid I might be overthinking it. I tried using a MultiValue converter without much luck. Any ideas?
You simply need a converter that casts the value to an index.
public class TabConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (int)value;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (AvailableTabs)value;
}
}
Instead of specifying the values in the XAML, I would bind ItemsSource to an array of values from your enum:
Code:
public AvailableTabs[] AvailableTabs => Enum.GetValues(typeof(AvailableTabs Enum)).Cast<AvailableTabs>().ToArray();
XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab}" ItemsSource="{Binding AvailableTabs}" />
The important thing is that the solution should work if I change the tab order in the TabControl
if this is not important then the elements in the enum type
and the views in the TabControl have to be in the same order
the conversion in this case is to cast the enum value to int value
I name the views in TabControl according to enum values
AppTab.ValidDates (enum value) corresponds to validDatesView (view name)
enum values
public enum AppTab
{
Parameters, ValidDates, ...
}
views in TabControl
<TabControl Name="myTabControl" SelectedIndex="{Binding SelectedTab, Converter={c:AppTabToIntConverter}}"
IsEnabled="{Binding IsGuiEnabled}">
<TabItem Header="{x:Static r:Resource.Parameters}">
<view:ParametersView x:Name="parametersView"/>
</TabItem>
<TabItem Header="{x:Static r:Resource.Dates}">
<view:ValidDatesView x:Name="validDatesView"/>
</TabItem>
fill up ViewNameIndexDictionary and IndexViewNameDictionary
Window_Loaded event is too late, AppTabToIntConverter runs before that
public static Dictionary<AppTab, int> ViewNameIndexDictionary { get; set; } = new Dictionary<AppTab, int>();
public static Dictionary<int, AppTab> IndexViewNameDictionary { get; set; } = new Dictionary<int, AppTab>()
private void Window_Initialized(object sender, EventArgs e)
{
var i = 0;
foreach (TabItem item in myTabControl.Items)
{
var tabContentName = ((FrameworkElement)item.Content).Name;
// Convert TabItem name "validDatesView" to "ValidDates"
var appTabString = tabContentName.FirstCharToUpper().CutLastNCharacter("View".Length);
var appTab = (AppTab)Enum.Parse(typeof(AppTab), appTabString);
ViewNameIndexDictionary.Add(appTab, i);
IndexViewNameDictionary.Add(i, appTab);
i++;
}
}
the converter
// The root tag must contain: xmlns:c="clr-namespace:LedgerCommander.ValueConverter"
class AppTabToIntConverter : BaseValueConverter<AppTabToIntConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is AppTab appTab))
throw new Exception("The type of value is not AppTab");
return MainWindowView.ViewNameIndexDictionary[appTab];
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int tabIndex))
throw new Exception("The type of value is not int");
return MainWindowView.IndexViewNameDictionary[tabIndex];
}
}
the BaseValueConverter (thanks to AngelSix)
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter where T : class, new()
{
private static T Converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Converter ?? (Converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

WPF DataGrid, customizing the display with a converter

I have a unique situation. In one class, I have an inner class that acts as pretty much just a "display" class. In the outer class there is a method called GetDisplayObject that returns a type of the inner class.
I'm trying to bind to a datagrid with the outer class, but by using a converter I'd like to get the correct display. This way I won't have to change a bunch of code in our application and just add a few lines in a couple .xaml files.
I made a little test app that pretty much sums up my problem at it's most basic level. Ideally I'd like to solve the problem by using a converter and returning values only as a display, that way when i'm using the SelectedItem I won't have to change a ton of code that is depending on that certain type(In this case would be the DataObject type).
So here is the objects i'm stuck dealing with
namespace TestApp
{
public class DataObject
{
public class DataObjectDisplay
{
public string ObjectDisplay { get; set; }
}
// props
public int Id { get; set; }
public object Object1 { get; set; }
public object Object2 { get; set; }
// method for getting the display
public DataObjectDisplay GetDisplayObject()
{
DataObjectDisplay display = new DataObjectDisplay();
// logic for determining which object should be displayed
if(Object1 == null)
{
display.ObjectDisplay = "Object1";
}
else
{
display.ObjectDisplay = "Object2";
}
return display;
}
}
}
Here is the Code Behind of my xaml
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
this.DataObjectCollection = new ObservableCollection<DataObject>();
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object1 = "this", Object2 = "that" });
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object2 = "that" });
this.SelectedItem = new DataObject();
}
private ObservableCollection<DataObject> dataObjectCollection;
private DataObject selectedItem;
public ObservableCollection<DataObject> DataObjectCollection
{
get { return this.dataObjectCollection; }
set
{
dataObjectCollection = value;
OnNotifyPropertyChanged("DataObjectCollection");
}
}
public DataObject SelectedItem
{
get { return this.selectedItem; }
set
{
selectedItem = value;
OnNotifyPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(string property = "")
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Here is the xaml(This is something like what i'd want to do, using the itemtemplate or something similar, and then a converter to call this GetDisplay function)
<Window x:Class="TestApp.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:TestApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DataObjectToDisplayDataObjectConverter x:Key="ToDisplayConverter"/>
</Window.Resources>
<StackPanel>
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Converter={StaticResource ToDisplayConverter}}"/>
</DataTemplate>
</DataGrid.ItemTemplate>
</DataGrid>
</StackPanel>
</Window>
And Finally the converter
using System;
using System.Globalization;
using System.Windows.Data;
namespace TestApp
{
public class DataObjectToDisplayDataObjectConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(DataObject))
{
DataObject dataObj = (DataObject)value;
dataObj.GetDisplayObject();
return dataObj;
}
return "Invalid Value";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I am open to suggestions, but this is a small example. In our actual application changing one thing will most likely cascade into a huge ordeal.
If I were you I would have change close coupling of view with ViewModel, Like creating new MainWindowViewModel class & having all properties in it.
Another thing I see GetDisplayObject method, what's the need of calling such a method from converter.
You can re-factor this code & put in the converter something like this.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DataObject dataObj = value as DataObject;
if (value == null)
{
return "Invalid Value";
}
if (dataObj.Object1 == null)
{
return "Object1";
}
return "Object2";
}
Luckily since it was a matter of if one object was null and it's related strings were empty, I was able to use Priority Binding with a converter to return DependencyProperty.UnsetValue and force it to use the next binding. I think this was the best solution for the situation.
So the xaml ended up looking like this.
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<PriorityBinding>
<Binding Path="Object1" Converter="{StaticResource EmptyStringToDependencyPropertyUnset}"/>
<Binding Path="Object2"/>
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the Converter ended up like this
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null || value as string == string.Empty)
{
return DependencyProperty.UnsetValue;
}
return value;
}

Select ItemTemplate in value converter

I'm trying to have multiple ItemTemplates in a listview based on each items property. But I keep getting {"Error HRESULT E_FAIL has been returned from a call to a COM component."} in my value converter:
public class EquipmentTemplateConverter : IValueConverter
{
public object Convert(object value, Type type, object parameter, string language)
{
switch ((EquipmentType) (int) value)
{
case EquipmentType.Normal:
return Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentNormalTemplate");
case EquipmentType.Upgrade:
return Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentUpgradeTemplate");
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
public object ConvertBack(object value, Type type, object parameter, string language)
{
throw new NotImplementedException();
}
}
XAML:
<DataTemplate x:Key="EquipmentTemplate" >
<Grid>
<ContentControl DataContext="{Binding}" Content="{Binding}" x:Name="TheContentControl" ContentTemplate="{Binding Equipment.Type, Converter={StaticResource EquipmentTemplateConverter } }" />
</Grid>
</DataTemplate>
Any ideas how I can solve this?
The usual way to do this is by writing a DataTemplateSelector and assigning an instance of it to ContentControl.ContentTemplateSelector.
<DataTemplate x:Key="EquipmentTemplate" >
<DataTemplate.Resources>
<local:EquipmentTemplateSelector x:Key="EquipmentTemplateSelector" />
</DataTemplate.Resources>
<Grid>
<ContentControl
DataContext="{Binding}"
Content="{Binding}"
x:Name="TheContentControl"
ContentTemplateSelector="{StaticResource EquipmentTemplateSelector}"
/>
</Grid>
</DataTemplate>
C#:
public class EquipmentTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// container is the container. Cast it to something you can call
// FindResource() on. Put in a breakpoint and use the watch window.
// I'm at work with Windows 7. Shouldn't be too hard.
var whatever = container as SomethingOrOther;
Object resKey = null;
// ************************************
// Do stuff here to pick a resource key
// ************************************
// Application.Current.Resources is ONE resource dictionary.
// Use FindResource to find any resource in scope.
return whatever.FindResource(resKey) as DataTemplate;
}
}
But I keep getting {"Error HRESULT E_FAIL has been returned from a call to a COM component."} in my value converter.
This error usually happens when a reference to a style or an event handler that dose not exist or is not within the context of the XAML.
You posted only the code of your converter and part of your xaml code, I can't 100% reproduce your data model and your xaml, but from your code, I think in your converter, you would like to return the specific DataTemplate, but you actually return a KeyValuePair<object, object>, resources are defined in ResourceDictionary follow the "key-value" parttern, for more information, you can refer to ResourceDictionary and XAML resource references.
Here I wrote a sample, again I didn't 100% reproduce your xaml and data model:
MainPage.xaml:
<Page.Resources>
<local:EquipmentTemplateConverter x:Key="EquipmentTemplateConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind list}">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl DataContext="{Binding}" Content="{Binding}" ContentTemplate="{Binding Count, Converter={StaticResource EquipmentTemplateConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
code behind:
private ObservableCollection<EquipmentType> list = new ObservableCollection<EquipmentType>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 1 });
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 1 });
list.Add(new EquipmentType { Count = 1 });
}
My EquipmentType class is quite simple:
public class EquipmentType
{
public int Count { get; set; }
}
and EquipmentTemplateConverter is like this:
public class EquipmentTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
switch ((int)value)
{
case 0:
var a = Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentNormalTemplate");
return a.Value;
case 1:
var b = Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentUpgradeTemplate");
return b.Value;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Since you are using Application.Resources property in your converter, I just put the DataTemplate in the App.xaml for test:
<Application.Resources>
<DataTemplate x:Key="EquipmentNormalTemplate">
<Grid>
<TextBlock Text="This is EquipmentNormalTemplate." />
</Grid>
</DataTemplate>
<DataTemplate x:Key="EquipmentUpgradeTemplate">
<Grid>
<TextBlock Text="This is EquipmentUpgradeTemplate." />
</Grid>
</DataTemplate>
</Application.Resources>
But I agree with #Ed Plunkett, using DataTemplateSelector is a more common way to do this work.

Bind object to ConverterParameter fails with XamlParseException in WP8.1 Silverlight

Initial situation
I have a Windows Phone 8.1 Silverlight app where I have a model that contains several properties as shown below (just an excerpt of 3 properties, it has a lot more).
public class WorkUnit : INotifyPropertyChanged
{
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("ToAsShortTimeString");
}
}
public string ToAsShortTimeString
{
get
{
if (To.HasValue)
{
if (Type == WorkUnitType.StartEnd)
return To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = To.Value - From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
}
public short? Type
{
get { return Get<short?>(); }
set
{
Set(value);
OnPropertyChanged("Type");
}
}
}
I'm using MVVMLight. There are several work units in an ObservableCollection that is bound to a list box on a Windows Phone page. The collection itself is part of a (WorkDay) view model which in turn is bound to the page itself.
What I want to do
I have a lot of properties in my model that are just used to format some properties for the UI. One such is ToAsShortTimeString which returns the time given by the To property, depending on the Type and the From properties, formatted as string.
In order to clean up my model I want to remove such formatter-properties as much as possible and use converters (IValueConverter) as much as possible. One further reason to move away from such properties is that the database that I use (iBoxDB) doesn't have member attributes like [Ignore] that is available for SQLite. So all properties with supported types are stored in the database. However, such formatter properties shouldn't be stored if possible.
What I did - 1st try
I now transformed all properties to converters and most of the time this was no problem. However, ToAsShortTimeString not just uses one property but 3 to format the input. Therefore, in XAML I need to provide either those 3 properties to the value converter or the work unit itself which is bound to the page.
public class WorkUnitToEndTimeStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var workUnit = (WorkUnit) value;
if (workUnit.To.HasValue)
{
if (workUnit.Type == WorkUnitType.StartEnd)
return workUnit.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = workUnit.To.Value - workUnit.From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So I changed the binding of the Text property in the TextBlock that shows the formatted To property to the WorkUnit that is bound to the page.
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit,Converter={StaticResource WorkUnitToEndTimeStringConverter}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
Unfortunately, when the To property changes in the model, even though OnPropertyChanged is called (see model code above), the text block doesn't get updated. I assume the reason is that only those controls are updated where some property is directly bound to the changed model property.
What I did - 2nd try
So as I need 3 properties from WorkUnit in order to correctly format To I changed the binding as follows. I bound Text to WorkUnit.To and set the ConverterParameter to the WorkUnit itself. With this change I hoped that whenever To is changed in the model and the value converter is called, I can format the time because I have all the info provided from the converter parameter (WorkUnit). (I'm not printing the updated converter here but I changed it to accomodate the change on the value and parameter input parameters)
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit.To,Converter={StaticResource WorkUnitToEndTimeStringConverter},ConverterParameter={Binding WorkUnit}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
Unfortunately, in this case a XamlParseException exception is thrown.
{System.Windows.Markup.XamlParseException: Failed to assign to property 'System.Windows.Data.Binding.ConverterParameter'. [Line: 61 Position: 18] ---> System.InvalidOperationException: Operation is not valid due to the current state of the object.
at MS.Internal.XamlManagedRuntimeRPInvokes.TryApplyMarkupExtensionValue(Object target, XamlPropertyToken propertyToken, Object value)
at MS.Internal.XamlManagedRuntimeRPInvokes.SetValue(XamlTypeToken inType, XamlQualifiedObject& inObj, XamlPropertyToken inProperty, XamlQualifiedObject& inValue)
--- End of inner exception stack trace ---
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)}
Question
So is there a way to remove the formatter-property from my model so that I can keep my model as clean as possible? Is there sth. wrong with my converter? Is there any other way that I currently don't know of?
You could have a property in your WorkUnit called EndTimeString
public string EndTimeString
{
get
{
string endTime = null;
if (this.To.HasValue)
{
if (this.Type == WorkUnitType.StartEnd)
{
endTime = this.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
}
else
{
var duration = this.To.Value - this.From;
endTime = DateHelper.FormatTime(duration, false);
}
}
return endTime
}
}
Of course, if To value changes, you want to notify the UI that the EndTimeString has also changed so that it picks up the new value:
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("EndTimeString");
}
}
And then just bind straight to that string:
...Text="{Binding WorkUnit.EndTimeString}" />
Unfortunately you can't bind to parameters.
This would be easy with MultiBinding but that's not available for Windows Phone (as you stated in your comment).
You could implement it yourself but if you are not really into it :), there are implementations trying to mimic this behaviour. One of them can be found from the joy of code.
There is a NuGet package called Krempel's WP7 Library which has above implemented for WP7 but it works on WP8.x as well.
Downside is that it can only bind to elements in visual tree, so you have to use (for lack of a better word) relaying UI elements to get the job done. I have used similar technique myself when I can't bind directly to a property I need to. One case is AppBar, you can't bind directly to enabled property, so instead I use something like
<CheckBox Grid.Row="0" IsEnabled="{Binding AppBarEnabled}"
IsEnabledChanged="ToggleAppBar" Visibility="Collapsed" />
Anyway, below is full example, without any groovy patterns, on how you can achieve multibinding using above library. Try it out and see if it's worth the trouble. Options are that you have "extra" properties in your model or some extra elements and complexity in your view.
I used your WorkUnit model and Converter to make it more useful and easier to understand.
Outcome should be something like this.
MainWindow.xaml
<phone:PhoneApplicationPage
x:Class="WP8MultiBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Krempel.WP7.Core.Controls;assembly=Krempel.WP7.Core"
xmlns:conv="clr-namespace:WP8MultiBinding"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<conv:WorkUnitToEndTimeStringConverter
x:Key="WorkUnitToEndTimeStringConverter" />
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="80" />
<RowDefinition Height="80" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Multibinding & converter -->
<controls:MultiBinding
x:Name="MultiBinding"
Converter="{StaticResource WorkUnitToEndTimeStringConverter}"
NumberOfInputs="3"
Input1="{Binding ElementName=Type, Path=Text, Mode=TwoWay}"
Input2="{Binding ElementName=From, Path=Text, Mode=TwoWay}"
Input3="{Binding ElementName=To, Path=Text, Mode=TwoWay}" />
<!-- Output from multibinded conversion -->
<TextBox Text="{Binding ElementName=MultiBinding, Path=Output}" Grid.Row="0" />
<!-- Update WorkUnit properties -->
<Button Click="UpdateButtonClick" Grid.Row="1">Test MultiBinding</Button>
<!-- Helper elements, might want to set visibility to collapsed -->
<StackPanel HorizontalAlignment="Center" Grid.Row="2">
<TextBlock x:Name="Type" Text="{Binding WorkUnit.Type, Mode=TwoWay}" />
<TextBlock x:Name="From" Text="{Binding WorkUnit.From, Mode=TwoWay}" />
<TextBlock x:Name="To" Text="{Binding WorkUnit.To, Mode=TwoWay}" />
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using Microsoft.Phone.Controls;
namespace WP8MultiBinding
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
WorkUnit = new WorkUnit()
{
To = DateTime.Now.AddHours(5),
From = DateTime.Now,
Type = WorkUnitType.StartEnd
};
}
public WorkUnit WorkUnit { get; set; }
// Ensure bindings do update
private void UpdateButtonClick(object sender, RoutedEventArgs e)
{
WorkUnit.Type = WorkUnit.Type == WorkUnitType.StartEnd ?
WorkUnit.Type = WorkUnitType.Other :
WorkUnit.Type = WorkUnitType.StartEnd;
WorkUnit.From = WorkUnit.From.AddMinutes(60);
if (WorkUnit.To.HasValue)
WorkUnit.To = WorkUnit.To.Value.AddMinutes(30);
}
}
public enum WorkUnitType
{
StartEnd,
Other
}
public class WorkUnit : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private WorkUnitType _type;
private DateTime _from;
private DateTime? _to;
public WorkUnitType Type
{
get { return _type; }
set { _type = value; OnPropertyChanged(); }
}
public DateTime From
{
get { return _from; }
set { _from = value; OnPropertyChanged(); }
}
public DateTime? To
{
get { return _to; }
set { _to = value; OnPropertyChanged(); }
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// Multivalue Converter
public class WorkUnitToEndTimeStringConverter : Krempel.WP7.Core.Controls.IMultiValueConverter
{
private const string DateFormat = "M/d/yyyy h:mm:ss tt";
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Index: 0 = Type, 1 = From, 2 = To
if (values[2] != null)
{
var type = (WorkUnitType) Enum.Parse(typeof (WorkUnitType), values[0].ToString());
var from = DateTime.ParseExact(values[1].ToString(), DateFormat, CultureInfo.InvariantCulture);
var to = DateTime.ParseExact(values[2].ToString(), DateFormat, CultureInfo.InvariantCulture);
if (type == WorkUnitType.StartEnd)
return to.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = to - from;
return duration; // DateHelper.FormatTime(duration, false);
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

Specify a default empty DataTemplate instead of the default 'ToString()' DataTemplate

The default DataTemplate in a wpf application displays the result of the .ToString() method. I'm developing an application where the default DataTemplate should display nothing.
I've tried:
<Grid.Resources>
<DataTemplate DataType="{x:Type System:Object}">
<Grid></Grid>
</DataTemplate>
</Grid.Resources>
But this doesn't work. Does anyone knows if this is possible without specifiing a specific DataTemplate for every class type in the application?
If you are using the MVVM pattern and have an abstract class which all your ViewModel classes derive from, you can use that class instead of System.Object:
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:VMBase}">
</DataTemplate>
</Grid.Resources>
I know of no way to do this. As per Joe's comment below, WPF specifically disallows specifying a DataTemplate for type Object.
Depending on your exact requirements, it may be easier to search for a DataTemplate that matches the specific type. If you find one, use it. Otherwise, display nothing. For example:
<ContentControl Content="{Binding YourContent}" ContentTemplateSelector="{StaticResource MyContentTemplateSelector}"/>
And in your selector (pseudo-code, obviously):
var dataTemplateKey = new DataTemplateKey() { DataType = theType; };
var dataTemplate = yourControl.FindResource(dataTemplateKey);
if (dataTemplate != null)
{
return dataTemplate;
}
return NulloDataTemplate;
I used Nullable, worked for my situation.
<DataTemplate DataType="{x:Type sys:Nullable}">
<!-- Content -->
</DataTemplate>
I'm not sure about replacing the default DataTemplate, but you can use a ValueConverter to pass display ToString in the case of certain types and an empty string otherwise. Here's some code (note that the typeb textblock doesnt have the converter on it to show what it looks like normally):
.xaml:
<Window x:Class="EmptyTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:EmptyTemplate"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<loc:AType x:Key="atype"/>
<loc:BType x:Key="btype"/>
<loc:TypeConverter x:Key="TypeConverter"/>
</Window.Resources>
<StackPanel>
<Button Content="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<Button Content="{Binding Source={StaticResource btype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource btype}}"/>
</StackPanel>
</Window>
.xaml.cs:
namespace EmptyTemplate
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class AType { }
public class BType { }
public class TypeConverter : IValueConverter
{
public DataTemplate DefaultTemplate { get; set; }
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(AType))
{
return value.ToString();
}
return DefaultTemplate;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Here a working example about how to do this using a selector (the best way IMO):
public class EmptyDefaultDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
var dataTemplateKey = new DataTemplateKey(item.GetType());
var dataTemplate = ((FrameworkElement) container).TryFindResource(dataTemplateKey);
if (dataTemplate != null)
return (DataTemplate) dataTemplate;
}
return new DataTemplate(); //null does not work
}
}
I discovered something accidentally. I was using a custom dependency property to set the Datacontext on a usercontrol that had a contentcontrol with Datatemplates based on types(entities in my case). Since I had several different kinds of entities my custom dependency property was
` typeof(object)
This was the device I used to bind to the datacontext of the ContentControl.
public object MySelectedItem
{
get { return (object)GetValue(Property1Property); }
set { SetValue(Property1Property, value); }
}
public static readonly DependencyProperty Property1Property
= DependencyProperty.Register(
"MySelectedItem",
typeof(object),
typeof(PromotionsMenu),
new PropertyMetadata(false)
);
Used like this:
MySelectedItem = SomeEntity;
I discovered I could also use it like this:
MySelectedItem = "some text";
And the contextcontrol would print some text as its context.
MySelectedItem = "";
works for a totally blank context.
`

Categories