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.
Related
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
}
I effectively have -
<UserControl ...>
<Grid>
<TreeView Name="nTree">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="NodeType" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image x:Name="icon" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
NodeType is a type returned by an underlying library. I would like to set the icon images source based on the name provided - however I can't modify NodeType to add a getter.
So ideally what I would like is the icon image source to be bound to a function on the UserControl class which receives the Name and returns an ImageSource.
i.e.
public partial class Panel : UserControl
{
public Panel(NodeType n)
{
nTree.Items.add(n);
}
public ImageSource GetIcon(string name)
{
...
}
}
This feels like it should be possible but i'm struggling to work it out. Assistance would be appreciated.
Unfortunately you can't bind to methods, you need to convert the method to a property
you can do this in several ways
the easiest would be to have: (though this should be on your VM not your V)
public ImageSource Icon
{
...
}
or you can use a value converter:(the best fit for what you are descibing)
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
GetImageLogic
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("One way only");
}
}
the 3rd option would be to use an CommandBinding :
then bind to the command
CommandBinding OpenCmdBinding = new CommandBinding(
ApplicationCommands.Open,
OpenCmdExecuted,
OpenCmdCanExecute);
this.CommandBindings.Add(OpenCmdBinding);
void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
String command, targetobj;
command = ((RoutedCommand)e.Command).Name;
targetobj = ((FrameworkElement)target).Name;
MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj);
}
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
I have created a user control that has a Label and a ComboBox. It is used like this:
<cc:LabeledComboBox
HeaderLabelContent="Months"
ItemsSource="{Binding AllMonths}"
SelectedValue="{Binding SelectedMonth}"/>
And here is what the UserControl XAML looks like:
<UserControl x:Class="CustomControls.LabeledComboBox" ...>
<UserControl.Resources>
<converters:MonthEnumToTextConverter x:Key="MonthEnumToTextConverter" />
</UserControl.Resources>
<DockPanel>
<Label x:Name="LblValue" DockPanel.Dock="Top"/>
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- TODO: Fix so that the Converter can be set from outside -->
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
In the comment above you can see my problem. The control is generic (the ComboBox can contain pretty much anything) but on the Binding inside the DataTemplate I have specified a Converter that is very specific.
How can I specify the Converter from outside the UserControl?
I'm hoping for some kind of solution using a dependency property like this:
<cc:LabeledComboBox
...
ItemConverter="{StaticResource MonthEnumToTextConverter}"/>
You may have an internal binding converter that delegates its Convert and ConvertBack calls to one set is settable as dependency property:
<UserControl ...>
<UserControl.Resources>
<local:InternalItemConverter x:Key="InternalItemConverter"/>
</UserControl.Resources>
<DockPanel>
...
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding
Converter={StaticResource InternalItemConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
The internal converter could look like this:
class InternalItemConverter : IValueConverter
{
public LabeledComboBox LabeledComboBox { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.Convert(
value, targetType, parameter, culture);
}
return value;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.ConvertBack(
value, targetType, parameter, culture);
}
return value;
}
}
And finally the dependency property code like this:
public partial class LabeledComboBox : UserControl
{
private static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register(
"ItemConverter", typeof(IValueConverter), typeof(LabeledComboBox));
public IValueConverter ItemConverter
{
get { return (IValueConverter)GetValue(ItemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public LabeledComboBox()
{
InitializeComponent();
var converter = (InternalItemConverter)Resources["InternalItemConverter"];
converter.LabeledComboBox = this;
}
}
You can create multiple datatemplates for the the combobox items and then you can control what and how you want to display your comboxitem like below
<DataTemplate DataType="{x:Type vm:MonthDataTypeViewModel}" >
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
<DataTemplate DataType={x:Type vm:OtherViewModel}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
If you do not have multiple viewmodels then you can use a template selector to select different data templates based on some property in your viewmodel.
OP here. Presenting the solution that I'll use until I find something better.
I don't specify only the Converter, but the whole DataTemplate:
<cc:LabeledComboBox>
<cc:LabeledComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</cc:LabeledComboBox.ItemTemplate>
</cc:LabeledComboBox>
And here's the ItemTemplate dependency property:
public partial class LabeledComboBox : UserControl
{
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(LabeledComboBox),
new PropertyMetadata(default(DataTemplate), ItemTemplateChanged));
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var labeledComboBox = (LabeledComboBox)d;
labeledComboBox.LstItems.ItemTemplate = (DataTemplate)e.NewValue;
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// ...
}
EDIT: reworked to not use my personal example to avoid confusion ...
On your user control code behind you could define your dependency property.
I don't know what type your converters derive from so change 'myConverterType' to the type of converters you use.
public bool ItemConverter
{
get { return (myConverterType)GetValue(IntemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register("ItemConverter", typeof(myConverterType),
typeof(LabeledComboBox), null);
In XAML you should then just be able to set the converter property as per your example. In my example it is used like this:
<cc:LabeledComboBox ItemConverter="{StaticResource theSpecificConverter}"/>
Then use this property, on your user control xaml, like this:
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={Binding ItemConverter, ElementName=UserControl}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have some problem in wpf application.
In XAML:
<Expander Header="SomeHeader" Style="{StaticResource ExpanderStyle}" IsExpanded="{Binding ElementName=Errors, Converter={StaticResource visibilityConverter}, Path=IsExpanded}" >
<RichTextBox ScrollViewer.VerticalScrollBarVisibility="Visible" Style="{StaticResource RichTextBoxStyle}" Foreground="Red" IsReadOnly="True">
<FlowDocument>
<Paragraph>
<ItemsControl ItemsSource="{Binding Path=Errors}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Style="{StaticResource ErrorTextBlockStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Expander>
In my ViewModelClass:
private List<string> errors;
public List<string> Errors
{
get { return errors; }
set
{
errors = value;
OnPropertyChanged("Errors");
}
}
In constructor:
public MainWindowViewModel()
{
if (IsInDesignMode) return;
Errors = new List<string>();
}
In test method:
private void TestExcute()
{
Errors = "Some error";
}
In this situation error message not displayed in wpf window. But if I change code in constructor to next:
public MainWindowViewModel()
{
if (IsInDesignMode) return;
Errors = new List<string>{"errorMessage1", "errorMessage2"};
}
Displayed:
errorMessage1
errorMessage2
What is the reason ?
I have new question. In this wpf application I also used Expander control. How create auto expand open, then Errors.count > 0?
I create converter :
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
LoadFile loadFile = (LoadFile)value;
if (loadingFile.ExcelErrors.Count > 0)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
LoadFile is a class where declared Errors property.
I think you made an error in your TestExcute while writing question did you mean to write Errors.Add("some error")?
If so then your ItemsControl wont react to change because there is no change on property Errors - setter is not invoked.
Change your List<string> to ObservableCollection<string> this class notifies that its content has change and UI will react to that.
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.
`