3
votes

I want create custom control add property Info and InfoTemplate for example.
I define ControlTemplate in generic.xaml using ContetnPresenter for Info property.
When I don't use InfoTemplate it's works fine, but when I apply ItemTemplate the content is represents as class name string. This same template applied to GroupBox work like expected. What do I wrong? I need some extra code in OnApplyTemplate?
Bellow is print-screen my app and sources. Red border is a GroupBox, blue is my Control. Green border is part of DataTemplate.

App print-screen

EDIT: For testing I create class MyGroupBox inherited form GroupBox and override method OnHeaderChanged

public class MyGroupBox : GroupBox
{
    protected override void OnHeaderChanged(object oldHeader, object newHeader)
    {
        //base.OnHeaderChanged(oldHeader, newHeader);
    }
}

In that situation GroupBox.Heder behave like my MyCustomControl and display text instead of control. So question is: What should I implement in my control event to work like I want?


MyCustomControl.cs

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication7
{

    public class MyCustomControl : ContentControl
    {
        static MyCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }

        public object Info
        {
            get { return (object)GetValue(InfoProperty); }
            set { SetValue(InfoProperty, value); }
        }

        public DataTemplate InfoTemplate
        {
            get { return (DataTemplate)GetValue(InfoTemplateProperty); }
            set { SetValue(InfoTemplateProperty, value); }
        }

        public static readonly DependencyProperty InfoProperty =
            DependencyProperty.Register(nameof(Info), typeof(object), typeof(MyCustomControl), new PropertyMetadata(null));

        public static readonly DependencyProperty InfoTemplateProperty =
            DependencyProperty.Register(nameof(InfoTemplate), typeof(DataTemplate), typeof(MyCustomControl), new PropertyMetadata(null));
    }
}

Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication7">

    <Style TargetType="{x:Type local:MyCustomControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                    <StackPanel>

                        <TextBlock FontWeight="Bold">Info</TextBlock>
                        <ContentPresenter ContentSource="Info"/>

                        <TextBlock FontWeight="Bold">Content</TextBlock>
                        <ContentPresenter/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

MainWindow.xml

<Window x:Class="WpfApplication7.MainWindow"
        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:WpfApplication7"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        DATA_CONTEXT
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate x:Key="dataTemplate">
            <Border BorderBrush="Green" BorderThickness="5">
                <ContentPresenter Content="{Binding}"/>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <StackPanel>

        <Border BorderBrush="Red" BorderThickness="4">
            <GroupBox HeaderTemplate="{StaticResource dataTemplate}">
                <GroupBox.Header>
                    <TextBlock Text="{Binding}"/>
                </GroupBox.Header>
            </GroupBox>
        </Border>

        <Border BorderBrush="Blue" BorderThickness="4">
            <local:MyCustomControl InfoTemplate="{StaticResource dataTemplate}">
                <local:MyCustomControl.Info>
                    <TextBlock Text="{Binding}"/>
                </local:MyCustomControl.Info>

                My content
            </local:MyCustomControl>
        </Border>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.Collections.Generic;
using System.Windows;


namespace WpfApplication7
{

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
1

1 Answers

2
votes

So scrapping my answer and starting over now that I have a better understanding of what is being requested. The idea here is to basically recreate a custom GroupBox like control. The issue is that the DataContext for the Info property in the custom control (which is basically the Header property for GroupBox) isn't coming out as the DataContext of the custom control itself like it is when a GroupBox is used.

So the issue is that the chunk of UI that you set to the Info property is never added as a logical child of the control so it doesn't get added in such a way as to inherit the DataContext as is happening for the identical code when used in a GroupBox. To do this just update your custom control class to the following:

public class MyCustomControl : ContentControl
    {
        static MyCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));  
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }

        public object Info
        {
            get { return (object)GetValue(InfoProperty); }
            set { SetValue(InfoProperty, value); }
        }

        public DataTemplate InfoTemplate
        {
            get { return (DataTemplate)GetValue(InfoTemplateProperty); }
            set { SetValue(InfoTemplateProperty, value); }
        }

        public static readonly DependencyProperty InfoProperty =
            DependencyProperty.Register(nameof(Info), typeof(object), typeof(MyCustomControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(MyCustomControl.OnHeaderChanged)));

        private static void OnHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (MyCustomControl)d; 
            obj.RemoveLogicalChild(e.OldValue);
            obj.AddLogicalChild(e.NewValue);
        }

        public static readonly DependencyProperty InfoTemplateProperty =
            DependencyProperty.Register(nameof(InfoTemplate), typeof(DataTemplate), typeof(MyCustomControl), new PropertyMetadata(null));
    }

This will fix the issue, however, it should be stated that it would likely be cleaner just to have derived from HeaderedContentControl rather than from ContentControl as HeaderedContentControl already has all of this set up for you right out of the box and also already has a Header and HeaderTemplate that you could use in place of your Info and InfoTemplate properties which would save you some code.

If you want to get this working without bothering with logical children and so forth you can just update the binding in your chunk of UI that you set Info to and have it use a RelativeSource that searches for the custom control ancestor and then has a path of "DataContext", that will manually override the whole issue although you would have to remember to do that every time.

My bet would be it would be best for you to just derive from HeaderedContentControl, it looks like that should have all of the functionality you are looking for already baked in.