Search code examples
c#wpfxamluser-controls

Changing some user control attributes in XAML doesn't take place before running the application


I have a custom UserControl which is basically just a fancy container that has a title and some contents:

My Custom <code>UserControl</code> as can be seen when code is running And this is how I use it in my XAML:

<local:MDCard Header="Some Title">
<Grid>
...
...
</Grid>
</local:MDCard>

The problem is that in design time (before running the code) this is what I see in the graphical XAML editor:

My Custom <code>UserControl</code> as can be seen before running the code

Notice that the title is not showing.

And this is the source code of my UserControl:

<UserControl x:Class="HealthAndWellBeing.MDCard"
         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:local="clr-namespace:HealthAndWellBeing"
         mc:Ignorable="d" 
         d:DesignHeight="300"
         d:DesignWidth="300"
         x:Name="self">
<UserControl.Template>
    <ControlTemplate TargetType="{x:Type UserControl}">
        <Grid>
            <Grid>
                <Grid.Effect>
                    <DropShadowEffect Direction="270" ShadowDepth="1" Opacity="0.2" BlurRadius="2"/>
                </Grid.Effect>
                <Grid>
                    <Grid.Effect>
                        <DropShadowEffect Direction="270" ShadowDepth="0" Opacity="0.12" BlurRadius="8"/>
                    </Grid.Effect>
                    <Grid>
                        <Grid.Effect>
                            <DropShadowEffect Direction="270" ShadowDepth="1" Opacity="0.14"/>
                        </Grid.Effect>
                        <Border Background="#FFFAFAFA" CornerRadius="2"/>
                    </Grid>
                </Grid>
            </Grid>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="#19000000">
                    <Label FontWeight="Bold" Foreground="#FF616161" Margin="10,10,10,10">
                        <ContentPresenter Content="{Binding Header}"/>
                    </Label>
                </Border>
                <ContentPresenter Grid.Row="1"/>
            </Grid>
        </Grid>
    </ControlTemplate>
</UserControl.Template>

And this is my code-behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace HealthAndWellBeing
{
    /// <summary>
    /// Interaction logic for MDCard.xaml
    /// </summary>
    public partial class MDCard : UserControl, INotifyPropertyChanged
    {
        public MDCard()
        {
            InitializeComponent();
            DataContext = this;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyChange(string PropertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
        }

        public string Header
        {
            get { return (string)GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }
        public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(MDCard), new PropertyMetadata("Title"));
    }
}

So Why is it that when I change the text of a Button it can be seen in the graphical XAML editor instantly, but changing the Header of my custom UserControl can only be seen at run time?


Solution

  • In essence, changing the Binding-Signature of your Label from

    <Label FontWeight="Bold" Foreground="#FF616161" Margin="10,10,10,10">
       <ContentPresenter Content="{Binding Header}"/>
    </Label>
    

    to

    <Label FontWeight="Bold" Foreground="#FF616161" Margin="10,10,10,10">
       <ContentPresenter Content="{Binding Header, RelativeSource={RelativeSource TemplatedParent}}"/>
    </Label>
    

    should / could solve the issue.

    After all you are not trying to resolve to a property Header of the ControlTemplate you are giving to a UserControl, but to a Descendant of a UserControl class that has a DependencyProperty called Header. This is why you should bind to the TemplatedParent, i.e. the actual UserControl descendant.

    In the case of a CustomControl you use the compile-time binding TemplateBinding, since you are templating this specific class.

    With UserControls however you use Bindings, which are resolved at runtime via System.Reflection. That's why you are supposed to tell it to look for a specific parent if it is not a property of that specific class. In this case the TemplatedParent.

    A better explanation between Binding vs TemplatedBinding however can be found here.

    I can only assume that the "real" WPF runtime libraries will simply help you resolve the actual DependencyProperty - the blend (VS designer) ones won't.