Converter with Dependency Properties - c#

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!

Related

Trigger by ElementName from other UserControl

I have two UserControls(A,B) and I want to get A_TextBox Errors in UserControl A from the UserControl B. Is it possible?
<Usercontrol x:Class="A" (...)>
<TextBox x:name="A_TextBox (...)/>
</Usercontrol>
<Usercontrol x:Class="B" (...)>
(...)
<Controls:A/>
<Button (...)>
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=A_TextBox }" value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
This code causes an error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=A_TextBox '. BindingExpression:Path=(0); DataItem=null; target element is 'Button' (Name=''); target property is 'NoTarget' (type 'Object')
I've created two UserControls, A and B. A has a textbox whose Text is bound to an integer property of the UserControl, and A also has public readonly dependency property HasError. I get an error saying that Validation.HasError can't be data-bound, so I'm updating that property manually in a text-changed event handler. I created and included the Integer property so that I can type "xx" into the textbox and cause Validation.HasError to be true. Anything with working validation will work the same.
In the common parent, I bind A.HasError to B.IsEnabled, via a boolean-negation value converter. I could have written a trigger like yours as well. The advantage of this approach, in addition to the fact that it works, is that the two UserControls don't have to know about each others' internals, and neither one is dependent on the other. In addition, if you want to create nine of these pairs in a ListBox's ItemTemplate, you can do that without any problems.
A.xaml
<TextBox
VerticalAlignment="Top"
TextChanged="IntegerTextBox_TextChanged"
Text="{Binding Integer, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}"
/>
A.xaml.cs
private void IntegerTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
HasError = Validation.GetHasError(sender as TextBox);
}
public bool HasError
{
get { return (bool)GetValue(HasErrorProperty); }
protected set { SetValue(HasErrorPropertyKey, value); }
}
internal static readonly DependencyPropertyKey HasErrorPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(HasError), typeof(bool), typeof(A),
new PropertyMetadata(false));
public static readonly DependencyProperty HasErrorProperty =
HasErrorPropertyKey.DependencyProperty;
public int Integer
{
get { return (int)GetValue(IntegerProperty); }
set { SetValue(IntegerProperty, value); }
}
public static readonly DependencyProperty IntegerProperty =
DependencyProperty.Register(nameof(Integer), typeof(int), typeof(A),
new PropertyMetadata(0));
MainWindow.xaml
<Window.Resources>
<local:NotConverter x:Key="Not" />
</Window.Resources>
<Grid>
<StackPanel>
<local:A
Integer="34"
x:Name="MyAInstance"
/>
<local:B
IsEnabled="{Binding HasError, ElementName=MyAInstance,
Converter={StaticResource Not}}"
/>
</StackPanel>
</Grid>
MainWindow.xaml.cs
public class NotConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !System.Convert.ToBoolean(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !System.Convert.ToBoolean(value);
}
}

ContentControl is failing to bind to DataTemplate

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.

Specify Converter inside DataTemplate in UserControl

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>

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}}" />

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