ContentControl is failing to bind to DataTemplate - c#

I'm unable to bind the <ContentControl Content={Binding Type}/> to the local resources <DataTemplate> when binding my custom property on my POCO class called Type (I'm hoping it's not down to the bad choice of naming).
ReportItemModel
public class ReportItemModel
{
public ReportItemModel(string name, ReportItemType itemType, Type type)
{
Name = name;
ItemType = itemType;
Type = type;
}
public string Name { get; }
public ReportItemType ItemType { get; }
public Type Type { get; }
}
XAML
<ContentControl Content="{Binding Type}" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type moduleTypes:ReportModule}">
<Label>Test</Label>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
The Type property is (as you've probably guessed) a System.Type and the output within my WPF application is always the output of ToString() which I'm guessing is down to a failed match of my <DataTemplate>. If I change the ContentControl to Content={Binding} and set the <DataTemplate> to accept the data type of the POCO class ReportItemModel it works as intended. The only difference I can see is that ReportItemModel has been instantiated where as the Type property has not.

The issue was down to the XAML DataType="{x:Type moduleTypes:ReportModule}" calling behind the scenes GetType() on a System.Type which would always return a System.RuntimeType as the object was never instantiated and always (at that point) a System.Type (a "Brain Fart" moment to say the least). I was able to get around the issue by using a ValueConverter on the binding <ContentControl Content={Binding Type} and returning the BaseClass name as a string. Using the BaseClass name, It was easy enough to use a couple ofDataTriggers which changed the ContentTemplate value to one of my matched custom DataTemplate's using a similar method shown here.

Related

How to applying a datatemplate automatically by type, but also using it by key in XAML?

I'm trying to populate the contents of an ItemControl with instances of various types, where each of them should have their own data template assigned. Now some of them might be nested within another type, or might appear at the root of the collection, but I'd like to use the same data template for both.
<ItemsControl Items="{Binding MyItems}">
<ItemsControl.Resources>
<DataTemplate Type="{x:Type MyType1}">
<!-- contains this stuff -->
</DataTemplate>
<DataTemplate Type="{x:Type MyType2}>
<!-- contains that stuff -->
</DataTemplate>
<HierarchicalDataTemplate Type="{x:Type MyType3WhichHostsType1}" ItemsSource="{Children}">
<SomeWrapperStuff>
<ItemsControl Items="{Children}" ItemsTemplate="HOW?" /> <--- this part
</SomeWrapperStuff>
</HierarchicalDataTemplate>
</ItemsControl.Resources>
</ItemsControl>
The problem is, once I assign a key to the data template of MyType1, it stops being resolved automatically for all instances at the root of the collection, and instead only works within the nested HierarchicalDataTemplate. Is there a XAML-way of telling the ItemsControl that it should both resolve the template for an item by type, while I can still reference it as static resource by key?
No. This is because when you specify a DataTemplate in a ResourceDictionary with a DataType and no x:Key, WPF creates an implicit value for x:Key equal to "{x:Type [value of DataType property]}". So your DataTemplate always has a key.
As such, when you explicitly provide a key value, you are effectively replacing that Type-based key with another key, which causes WPF to no longer match the DataTemplate to its intended elements.
A workaround is to clone the DataTemplate, specifying x:Key in one and omitting it in the other.
This is a follow up to my comment here should anyone else looking for a similiar solution, however I'm leaving the accepted answer since I specifically asked for a pure XAML solution.
This simple DataTemplateSelector allows to assign several templates by type (with also checking whether an item is of a derived type of a template key-pair specified), while still allowing to assign a key to them.
public class TypeDataTemplateSelector : DataTemplateSelector {
public Collection<TypeDataTemplate> Templates { get; } = new Collection<TypeDataTemplate>();
public override DataTemplate SelectTemplate(object item, DependencyObject container) =>
item != null
? Templates.Where(templatePair => IsDerivedOfType(item.GetType(), templatePair.Type))
.Select(templatePair => templatePair.Template).FirstOrDefault() : null;
private static bool IsDerivedOfType(Type itemType, Type baseType) {
while (true) {
if (itemType == baseType) return true;
if (itemType.BaseType == null) return false;
itemType = itemType.BaseType;
}
}
}
public class TypeDataTemplate {
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
Example usage:
<MyControl.Resources>
<DataTemplate x:Key="SomeTemplate1" />
<DataTemplate x:Key="SomeTemplate2" />
<HierarchicalDataTemplate x:Key="SomeTemplateWithChildren" ItemsSource="...">
<ItemsControl ItemsSource="..." ItemsTemplate="{StaticResource SomeTemplate1}" />
</HierarchicalDataTemplate>
<TypeDataTemplateSelector x:Key="MyTypeDataTemplateSelector">
<TypeDataTemplateSelector.Templates>
<TypeDataTemplate Type="{x:Type SomeType1}" Template="{StaticResource SomeTemplate1}" />
<TypeDataTemplate Type="{x:Type SomeType2}" Template="{StaticResource SomeTemplate2}" />
<TypeDataTemplate Type="{x:Type SomeTypeWithChildren}" Template="{StaticResource SomeTemplateWithChildren}" />
</TypeDataTemplateSelector.Templates>
</TypeDataTemplateSelector>
</MyControl.Resources>
...
<ItemsControl Items="{Binding SomeItems}" ItemTemplateSelector="{StaticResource MyTypeDataTemplateSelector}" />

How to format a string in XAML without changing viewmodel's property getter?

I have in my application the following interface:
public interface IContactMedium
{
string ContactString { get; set; }
string Type { get; set;}
bool IsValid();
}
This interface is for objects that represent some sort of contact for a person. It could be a phone, email, etc. The ContactString property is the actual contact data (for a phone, for example, it would be the phone number), and the Type is for differentiation in case a person has more than one (for phone, a person can have a Home phone, a Work phone, Cell phone, etc.) The IsValid method is a validation mechanism for each different type of contact medium.
so, let's say I have two objects in my application - Email and Phone - both implement the interface. I'm going to make in the application a UserControl that holds a UI that manages a list of such objects. So the viewmodel would look something like this:
public class ContactsCollectionViewModel<T> : ViewModelBase where T : class, IContactMedium
{
private ObservableCollection<T> _itemsCollection;
public ContactCollectionViewModel(ObservableCollection<T> items)
{
ItemsCollection = items;
}
public ObservableCollection<T> ItemsCollection
{
get { return _itemsCollection; }
set
{
if (_itemsCollection != value)
{
_itemsCollection = value;
OnPropertyChanged(() => ItemsCollection);
}
}
}
}
I want to add to the IContactMedium interface another property/method that provides proper formatting for the ContactString property when used in Binding in WPF. The idea is that the format in the text box bound to ContactString differs depending on the concrete object that is actually stored in the collection:
<TextBox x:Name="ContactString"
Text="{Binding ContactString, StringFormat=???}" />
I searched online a solution for this and couldn't find anything. I saw people suggesting modifying the ContactString property so the getter returns a formatted value. So, for the Phone object, for example, the property would look like this:
public string ContactString
{
get
{
return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
}
set {
_contactString = value;
}
}
However, this is not a good solution for me. The information is not only used by the UI. It is also sent to other parts of the application, including a database, that need the phone number in its raw form: ##########.
Is there a way to provide the XAML a formatter to use in the StringFormat attribute of the binding? Can the formatting be dictated by the object that implement the interface? If yes, what type does it need to be, and how can I make it accessible to the Binding in XAML?
Can the formatting be dictated by the object that implement the interface?
In Xaml one can provide data templates which are associated with a specific class.
Simply provide the structure in the template with a formatting on the binding to the target property as shown below:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type c:Ship}">
<TextBlock Text="{Binding Path=Name, StringFormat=Ship: {0}}"
Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type c:Passage}">
<TextBlock Text="{Binding Path=Name, StringFormat=Passage: {0}}"
Foreground="Blue" />
</DataTemplate>
</Grid.Resources>
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding OBSCollection}">
</ListBox>
</Grid>
So for my collection where both class instances of Ship and Passage adhere to ITreeEntity:
public ObservableCollection<ITreeEntity> OBSCollection ...
When bound creates a list where the binding has a specific string format as such:
Note in setting up the data the ships were added first followed by the passages. Xaml is not ordering them in anyway.
Need to list different types objects in one ListBox from a composite collection? See my answers here:
Composite Collection ListBox Answer
Basic example of Listbox & Templates
The thing is that each concrete class that implements the interface would have different formatting rules
Can the formatting be dictated by the object that implement the interface?
The dilema is, whether to add the formating logic to your business objects (IContactMedium implementations) or to presentation layer.
if it is business logic, then yes, you should add the formatting code to your business object.
But most probably it is presentation logic. In that case, either create DataTemplate foreach implementation of the IContactMedium, or create converter. In the converter you can choose correct formatting based on the value type. If the output is just plain text, use converter. If its more that plain text, e.g formatted text, use datatemplates.
TIP: You can use unit tests to test if all implementations of IContactMedium have its DataTemplate or are covered by the converter.
You can use converters. Keep your property simple.
public string ContactString { get; set; }
Implement converter
class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
contactString = value as string;
if(contactString == null)
{
throw new InvalidArgumentException();
}
return string.Format("({0}) {1}-{2}",
contactString.Substring(0,3), contactString.Substring(4,3),
contactString.Substring(7,3));
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add it as resource
<Window.Resources>
<local:MyConverter x:Key="MyConverter"/>
</Window.Resources>
Use it
<TextBox x:Name="ContactString"
Text="{Binding ContactString, Converter{StaticResource MyConverter}}" />
You can simply override the ToString() method. By default, a ListBox will use the object's ToString() method as the display text for the item.
public override string ToString()
{
return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
}
This means you don't have to do anything fancy in the ListBox, like defining a DataTemplate, as the ListBox will pick up the formatted string automatically.
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding OBSCollection}"/>

DataTemplate for an array or IEnumerable

I would like to create an implicit DataTemplate that works on an array or IEnumerable of my class. This way I have a template that describes how to render a bunch of items instead of just one. I want to do this so I can, among other things, show the results in a tooltip. eg
<TextBlock Text="{Binding Path=CustomerName}" ToolTip="{Binding Path=Invoices}">
The tooltip should see that Invoices is a bunch of items and use the appropriate data template. The template would look something like this:
<DataTemplate DataType="{x:Type Customer[]}">
<ListBox "ItemsSource={Binding}">
etc
That didn't work so I tried the example from this post x:Type and arrays--how? which involves creating a custom markup extension. This works if you specify the key but not for an implicit template
So then I tried making my own custom markup extension inheriting from TypeExtension like below but I get an error that says "A key for a dictionary cannot be of type 'System.Windows.Controls.StackPanel'. Only String, TypeExtension, and StaticExtension are supported." This is a really weird error as it is taking the content of the datatemplate as the key?? If I specify a key then it works fine but that largely defeats the purpose.
[MarkupExtensionReturnType(typeof(Type)), TypeForwardedFrom("PresentationFramework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public class ArrayTypeExtension
: TypeExtension
{
public ArrayTypeExtension() : base() { }
public ArrayTypeExtension(Type type) : base(type)
{
}
public ArrayTypeExtension(string value) : base(value)
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
Type val = base.ProvideValue(serviceProvider) as Type;
return val == null ? null : val.MakeArrayType();
}
}
As noted in the question you linked to {x:Type ns:TypeName[]} works. It may screw over the designer but at runtime it should be fine.
To avoid designer errors the template can be moved to App.xaml or a resource dictionary (or of course just don't use the designer at all).
(The error mentioning the control inside the template sounds like a bug in the code generator or compiler, sadly i doubt that there is much you can do about that one.)
If you are OK with creating your own type, I just tried and following and it is working. Create a specific type for your collection:
public class InvoiceCollection : List<Invoice> { }
public class Customer {
public string name { get; set; }
InvoiceCollection invoices { get; set; }
}
and then the XAML with data template:
<DataTemplate DataType={x:Type InvoiceCollection}>
<ListBox ItemsSource="{Binding}" />
</DataTemplate>
<TextBox Text="{Binding name}" Tooltip="{Binding invoices}" />

System.Linq.GroupBy Key not binding in silverlight

list.ItemsSource=db.Templates.GroupBy(t=>t.CategoryName);
in xaml:
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
After this code. Don't show any text in TextBlock. I'm changing Text binding like this
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
TextBlock Text shown like this System.Linq.Lookup^2+Grouping[System.String,Model.Template]
I'm debugging and checking Key property. this is not null.
Why Key don't bind in TextBlock?
How to show group title in Textblock?
Hmmm - unfortunate. The reason is because the result of the GroupBy() call is an instance of System.Linq.Lookup<,>.Grouping. Grouping is a nested class of the Lookup<,> class, however Grouping is marked as internal.
Security restrictions in Silverlight don't let you bind to properties defined on non-public types, even if those properties are declared in a public interface which the class implements. The fact that the object instance you are binding to is of a non-public concrete type means that you can only bind to public properties defined on any public base classes of that type.
You could build a public shim class to act as a view model for the grouping:
public class MyGrouping {
public string Key {get; internal set;}
}
list.ItemsSource=db.Templates.GroupBy(t=>t.CategoryName)
.Select(g => new MyGrouping { Key = g.Key });
It's been a while, but I had similar problem recently, so I decided to post another solution.
You can create a converter and return the value of Key from it
public class GroupNameToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var grouping = (IGrouping<string, [YOUR CLASS NAME]>) value;
return grouping.Key;
}
}
and in Xaml you don't bind to Key, but to the grouping itself.
<TextBlock Text="{Binding Converter={StaticResource groupNameToStringConverter}}" />

Converter with Dependency Properties

I have problems implementing a custom DependencyObject:
I need a converter which sets or unsets a enum flag in a bound property. Therefore I created a IValueConverter derieved from FrameworkElement with two DependencyProperties: Flag (the flag which is set/unset by the converter) and Flags (the value/property to modify). The parent UserControl (Name = EnumerationEditor) provides the property to which the converter is bound.
A ListBox generates CheckBoxes and the converter instances which are used to modify the property via a DataTemplate. Each CheckBox/converter instance is used for one flag. I use the following XAML code:
<ListBox Name="Values" SelectionMode="Extended" BorderThickness="1" BorderBrush="Black" Padding="5">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type system:Enum}">
<DataTemplate.Resources>
<Label x:Key="myTestResource" x:Shared="False"
Content="{Binding}"
ToolTip="{Binding Path=Value, ElementName=EnumerationEditor}"
Foreground="{Binding Path=Background, ElementName=EnumerationEditor}"
Background="{Binding Path=Foreground, ElementName=EnumerationEditor}"/>
<converters:EnumerationConverter x:Key="EnumerationConverter" x:Shared="False"
Flag="{Binding}"
Flags="{Binding Path=Value, ElementName=EnumerationEditor}"/>
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding}" IsChecked="{Binding Path=Value, ElementName=EnumerationEditor, Converter={StaticResource EnumerationConverter}}"/>
<ContentPresenter Content="{StaticResource myTestResource}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The strange thing: The Label works fine - but the converter does not. I get the error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=EnumerationEditor'. BindingExpression:Path=Value; DataItem=null; target element is 'EnumerationConverter' (Name=''); target property is 'Flags' (type 'Enum')
I don't understand why, the binding is basically the same...
Here is the code for the converter:
public class EnumerationConverter : FrameworkElement, IValueConverter
{
#region IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Parity.Space;
}
#endregion
#region public Enum Flag { get; set; }
public Enum Flag
{
get { return (Enum)this.GetValue(EnumerationConverter.FlagProperty); }
set { this.SetValue(EnumerationConverter.FlagProperty, value); }
}
/// <summary>
/// Dependency property for Flag.
/// </summary>
public static readonly DependencyProperty FlagProperty = DependencyProperty.Register("Flag", typeof(Enum), typeof(EnumerationConverter));
#endregion
#region public Enum Flags { get; set; }
public Enum Flags
{
get { return (Enum)this.GetValue(EnumerationConverter.FlagsProperty); }
set { this.SetValue(EnumerationConverter.FlagsProperty, value); }
}
/// <summary>
/// Dependency property for Flags.
/// </summary>
public static readonly DependencyProperty FlagsProperty = DependencyProperty.Register("Flags", typeof(Enum), typeof(EnumerationConverter));
#endregion
}
A converter is not a FrameworkElement so it should not inherit from that class, at best use DependencyObject.
Since the converter is not in any tree that binding will not work, you can try:
<converters:EnumerationConverter x:Key="EnumerationConverter" x:Shared="False"
Flag="{Binding}"
Flags="{Binding Path=Value, Source={x:Reference EnumerationEditor}}"/>
(However this should be placed in the Resources of the UserControl and referenced, otherwise the x:Reference will cause a cyclical dependency error.)
Note that the Flag binding tries to bind to the DataContext which might not work as the DataContext may not be inherited for the same reasons that ElementName and RelativeSource will not work.
Conclusion
I decided to solve the problem using two UserControls; FlagControl and EnumerationEditorControl.
The FlagControl has two dependency properties
Flag (System.Enum): Determines which flag is set/cleared by the control
Value(System.Enum): Bound to the propery/value in which the flag is set/cleared.
The EnumerationEditorControl has one dependency property:
Value(System.Enum): The propery/value in which flags are set.
The EnumerationEditorControl uses a DataTemplate to instantiate FlagControls. The DataTemplate binds the FlagControl.Flag property to the DataContext and the FlagControl.Value property to the EnumerationEditorControl.Value property.
This way I don't need a converter and logic is clearly separated.
Thanks for the suggestions, comments and replies!

Categories