Torbjörn Kalin
Torbjörn Kalin

Reputation: 1986

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

Upvotes: 1

Views: 8069

Answers (4)

Clemens
Clemens

Reputation: 128023

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;
    }
}

Upvotes: 2

Mashton
Mashton

Reputation: 6415

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>

Upvotes: 0

Torbj&#246;rn Kalin
Torbj&#246;rn Kalin

Reputation: 1986

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); }
    }

    // ...
}

Upvotes: 0

Krishna
Krishna

Reputation: 1996

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.

Upvotes: 1

Related Questions