Search code examples
c#wpfxamldata-bindingcontentcontrol

Why is my custom ContentControl with data triggers not showing up in the UI, though the constructor is called?


I'm using reflection and expression trees in C# to build a fairly adaptable search tool for our database. Because of this, I have a need for a custom ContentControl - termed 'MultiStyleInputBox' - which uses data triggers to adjust its ContentTemplate to the Type of input expected. The problem is, while the code builds just fine and I have confirmed that both the public and static constructors are being hit when the code executes, the ContentControl's content doesn't show up at all in my UI.

Now, I'm relatively new to writing custom XAML/C# UI control classes, but I have been able to cobble together the following:

<ContentControl x:Class="MyApp.MultiStyleInputBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             Name="multiStyleInputBox">
    <ContentControl.Resources>
        <Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
               <!--I have several of these triggers for different data types-->
            </Style.Triggers>
            <Style.Setters>
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style.Setters>
        </Style>
    </ContentControl.Resources>
</ContentControl>

And the code behind:

    public sealed partial class MultiStyleInputBox : ContentControl
    {
        //Dependency properties
        public Type InputType
        {
            get { return (Type)GetValue(InputTypeProperty); }
            set { SetValue(InputTypeProperty, value); }
        }
        public static readonly DependencyProperty InputTypeProperty =
            DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
        public object Value
        {
            get { return (object)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox));

        //Constructors
        public MultiStyleInputBox() : base()
        {

        }
        static MultiStyleInputBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox), 
                new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
        }
    }

I thought at one point that I might not have set the content of the ContentControl, and so I added a <ContentPresenter/>, but I received an error saying that the content is set more than once, so I believe that my <Style.Setters></Style.Setters> section is taking care of that. Otherwise, even running around using PresentationTraceSources.TraceLevel="High" on my bindings, I haven't so far been able to run into any useful errors.

Is there some sort of glaring issue in my code that I can immediately address (hopefully)? Do I need to reevaluate my approach to the problem?


Update

After suggested corrections in the answers below, here is the latest version of the code:

<ContentControl x:Class="MyApp.MultiStyleInputBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             Name="multiStyleInputBox">
    <ContentControl.Style>
        <Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox, Value="{x:Type sys:DateTime}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
                <!--I have several of these triggers for different data types-->
            </Style.Triggers>
            <Style.Setters>
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style.Setters>
        </Style>
    </ContentControl.Style>
</ContentControl>

And the code-behind:

public partial class MultiStyleInputBox : ContentControl
    {
        //Dependency properties
        public Type InputType
        {
            get { return (Type)GetValue(InputTypeProperty); }
            set { SetValue(InputTypeProperty, value); }
        }
        public static readonly DependencyProperty InputTypeProperty =
            DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
        public object Value
        {
            get { return GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
                new FrameworkPropertyMetadata(DateTime.Now,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        //Constructors
        public MultiStyleInputBox() : base()
        {

        }
        static MultiStyleInputBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox), 
                new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
        }
    }

Here is a test instantiation of the MultiStyleInputBox (I'm using Mahapps.Metro):

<Controls:MetroWindow
    xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
    x:Class="MyApp.TestWindow"
        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:MyApp"
        Title="Test Window" Height="450" Width="800"
        xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <StackPanel>
        <local:MultiStyleInputBox x:Name="TestMultiBox" Value="1" InputType="{x:Type sys:Int32}"/>
    </StackPanel>
</Controls:MetroWindow>

When I try to instantiate this class, I'm still not getting anything showing up in my UI, and the ContentControl isn't taking up any space. Even if I include Width="50" Height="24", I still get nothing. I've tested setting both Value and InputType in code-behind and using a breakpoint to inspect the object, and I'm finding that, while both values get set, the Content of the ContentControl remains null.


Solution

  • The immediate problem is that your style isn't applied to the ContentControl you want to apply it to. You're defining an implicit ContentControl style which will be applied to any ContentControls you create in the content of this control -- but you aren't creating any, and that's not what you want anyhow.

    For a quick fix, just change ContentControl.Resources to ContentControl.Style.

    <ContentControl x:Class="MyApp.MultiStyleInputBox"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:sys="clr-namespace:System;assembly=mscorlib"
                 Name="multiStyleInputBox">
        <ContentControl.Style>
            <Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
                        <Setter Property="ContentTemplate">
                            <Setter.Value>
                                <DataTemplate>
                                    <DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
                                </DataTemplate>
                            </Setter.Value>
                        </Setter>
                    </DataTrigger>
                   <!--I have several of these triggers for different data types-->
                </Style.Triggers>
                <Style.Setters>
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style.Setters>
            </Style>
        </ContentControl.Style>
    </ContentControl>
    

    Your next problem will be that selecting a new DateTime in the DatePicker won't update a property bound to the Value property of your viewmodel. Here's the fix for that:

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
            new FrameworkPropertyMetadata(DateTime.Now, 
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    

    And the last problem (or the first, perhaps) was that you weren't calling InitializeComponent() in the constructor, which is always required in any WPF codebehind class:

        public MultiStyleInputBox()
        {
            InitializeComponent();
        }