Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically display a control depending on bound property using WPF

Tags:

.net

wpf

I've got a property which is a database data type (char, DateTime, int, float etc...) and I want to change the control used to enter a value of the selected type. So for text values I want a TextBox and for date values I want a DatePicker.

One way I thought about doing it was to have one of each control on my form and set their Visibility using an appropriate IValueConverter implementation. I know this will work, but it would create a lot of code and doesn't feel very nice.

The other way I thought was to use a ContentPresenter and set its content with a Style and DataTriggers but I can't get it to work.

<Style x:Key="TypedValueHelper" TargetType="{x:Type ContentPresenter}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Char">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Date">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Integer">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

If anyone can fill in my "???" or offer a better solution please do.

like image 782
Jon Mitchell Avatar asked May 26 '10 09:05

Jon Mitchell


2 Answers

You could do a combination of style with setters and DataTemplates. You basically have the start for it in your code, although I don't think ContentPresenter is the right control to style, since it does not have a template.

Create a style like this:

<Style x:Key="TypedValueHelper" TargetType="{x:Type ContentControl}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Char">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=.}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Integer">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Slider Maximum="100" Minimum="0" Value="{Binding Path=.}"
                                         Orientation="Horizontal" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>
</Style>

Then use the style in the ContentControl:

<ContentControl Content="{Binding MyValue}"
                        Style="{StaticResource TypedValueHelper}">
like image 78
ThomasAndersson Avatar answered Oct 01 '22 17:10

ThomasAndersson


While the Style solution might work, the proper way to implement the dynamic content behavior would be to use DataTemplates as Sdry suggested. However, you would be using an enumeration to determine which DataTemplate to use, which essentially means you want to map a single type to multiple DataTemplates. This problem is solved by the DataTemplateSelector class, the following description is straight from MSDN:

"Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object."

You dynamic content should be hosted by a ContentControl like this:

<ContentControl Content="{Binding Path=ReferenceToYourViewModel}" ContentTemplateSelector="{DynamicResource MyTemplateSelector}"/>

The implementation of MyTemplateSelector:

public class MyTemplateSelector: DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement elem = container as FrameworkElement;
        if(elem == null)
        {
            return null;
        }
        if (item == null || !(item is YourViewModel))
        {
            throw new ApplicationException();
        }
        if ((item as YourViewModel).DataType == DataType.Char)
        {
            return elem.FindResource("CharDataTemplate") as DataTemplate;
        }
        if ((item as YourViewModel).DataType == DataType.Date)
        {
            return elem.FindResource("DateDataTemplate") as DataTemplate;
        }
        if ((item as YourViewModel).DataType == DataType.Integer)
        {
            return elem.FindResource("IntegerDataTemplate") as DataTemplate;
        }
        throw new ApplicationException();
    }
}

Then as you would expect, here are the DataTemplates to pick from:

<DataTemplate x:Key="CharDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>
<DataTemplate x:Key="DateDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>
<DataTemplate x:Key="IntegerDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>

With this, the appropriate DataTemplate will be chosen based on the DataType Property of your View Model. Which in my opinion is a lot cleaner than using Visibility or Styles.

like image 40
Bojin Li Avatar answered Oct 01 '22 18:10

Bojin Li