-1

Edit:
How is the proper way to create a Control to avoid the following problem with the ElementName Binding:

    <TextBox x:Name="MyTextBox" Text="some Text"></TextBox>
    <Label>
        <!--Binding works-->
        <TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
    </Label>
    <Button>
        <!--Binding works-->
        <TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
    </Button>
    <local:MyUserControl>
        <!-- THIS BINDING FAILS !!!-->
        <TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
    </local:MyUserControl>

MyUserControl.xaml:

<UserControl x:Class="Problem.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />

MyUserControl.xaml.cs

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }
}

Original: Im still new to WPF and could not find out how to do this properly. I basically want a UserControl's child to retain the behavior of being able to bind to the root element's x:Name in a XAML.

This is an example that shows the problem caused by my UserControl Descriptor compared to the WPF Controls:

<Parent x:Name="_thisParent">
...
    <Label>
        <!--  Binding to _thisParent works  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
    </Label>
    <uc:Descriptor Text="description: ">
        <!--  Binding to _thisParent FAILS !!  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
    </uc:Descriptor>
    <Button>
        <!--  Binding to _thisParent works  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
    </Button>

Here is the Code for my UserControl:

Descriptor.xaml

<UserControl
x:Class="EmbedContent.UserControls.Descriptor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptor">
<UserControl.Template>
    <ControlTemplate>
        <DockPanel>
            <TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
            <ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
        </DockPanel>
    </ControlTemplate>
</UserControl.Template>

Descriptor.xaml.cs

public partial class Descriptor : UserControl
{

    #region Ctor
    public Descriptor()
    {
        InitializeComponent();
    }
    #endregion



    #region Dependency-Properties
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
    #endregion

How would i need to implement a custom UserControl / ContentControl (if needed without xaml) to retain the behavior of the WPF Controls?
How is this done according to best practice anyway? I assume i only run into this problem because i'm doing something wrong.

negatic
  • 33
  • 8
  • You can use [binding proxy](https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/) when inside some other context or [relative source lookup](https://stackoverflow.com/q/15789090/1997232). – Sinatr Jul 09 '21 at 07:47
  • hmm.. you use 'x:Name=_this' in 'Parent' and in you UserControl too.. Maybe you go better with relativesource (ancestortype=*parent*); and in Button you set the Content Property, here you set the Text Property in both... not sure, wether this manner :-) – dba Jul 09 '21 at 09:15
  • Be aware that `Mode=TwoWay` and `UpdateSourceTrigger=PropertyChanged` are pointless on the Bindings of a TextBlock.Text or ContentPresenter.Content properties. – Clemens Jul 09 '21 at 10:09
  • Besides that you would usually bind the Text in the Descriptor's Context like ``, assuming that MyText is a property of the object in the DataContext of the parent element. You would then call that object a view model. – Clemens Jul 09 '21 at 10:15
  • @dba good catch in this example it would cause a problem, cause he would directly bind to Usercontrols _this. I tested this with different x:Names tho so the problem is still the same. Ancestor types are fine and good but that would mean i would always need to set it over ancestor, while if you forget it or don't know it and use it like any normal XAML element your binding breaks. – negatic Jul 09 '21 at 10:42
  • @Clemens I didn't know it was useless there. I also do not want to set or use the DataContext here since i heard its a bad practice to set DataContext for UserControls, that are used at multiple places and can contain content objects themselves. – negatic Jul 09 '21 at 10:50
  • I'm not talking about the DataContext of the UserControl, but the DataContext of the parent element, which (if not set explicitly) would hold the value inherited from the top level view element, e.g. a Window. – Clemens Jul 09 '21 at 10:58
  • @Clemens if i use that DataContext instead it would work correctly that's right. But that would not change the fact, that there is an unexpected break of the binding, if i want to use the x:Name of some element int that XAML, that's not child of the same Descriptor element. And i want to implement the UserControl in a way that will not cause unexpected problems when used by a third party. This same problem does not occur with elements like WPF's own Button. – negatic Jul 09 '21 at 11:11
  • Not sure what you are trying to say. I am not suggesting to set the DataContext of the Descriptor control. Is there any other UserControl you are referring to? – Clemens Jul 09 '21 at 11:16
  • @Clemens im trying to say that your suggestion how to get around this problem by using the DataContext of the application should be working fine. But that i am not searching for a workaround with the help of the DataContext but a way to create my UserControl in a way so that it acts similar to the Controls from WPF like Button. Right now my UserControl seems to act like a barrier restricting the scope of the x:Name to outside the UserControl even if its child is in the same XAML. – negatic Jul 09 '21 at 11:34
  • But it does already work... -- Setting the DataContext of a Window is totally ok, and will not break any Binding. Just don't set the DataContext of a UserControl - which you are not doing anyway. – Clemens Jul 09 '21 at 11:40
  • @Clemens i dno why this question is received so negative but i edited it again. Is the question clear now or why is this such a bad question? – negatic Jul 10 '21 at 08:32

2 Answers2

0

I finally found a solution but it still seems somewhat ugly so i expect someone to post a better solution. I post the code here including an example highlighting the problem again.

Solution:
split UserControl / ContentControl up into a .cs class inheriting from e.g. ContentControl and set the Template describing the extra elements in the App.xaml Resources. Basically move the .xaml part as style into Resources.

Descriptor.cs

namespace EmbedContent.UserControls
{
class Descriptor : ContentControl{
    #region Ctor
    public Descriptor() : base() {}
    #endregion

    #region Dependency-Properties
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
    #endregion
}
}

App.xaml

<Application
x:Class="EmbedContent.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmbedContent"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
StartupUri="MainWindow.xaml">
<Application.Resources>
    <Style TargetType="uc:Descriptor">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="uc:Descriptor">
                    <DockPanel>
                        <TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=uc:Descriptor}}" />
                        <ContentPresenter />
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Application.Resources>

The above can call and consume the Descriptor as i expect it from a WPF Control. Here the Code for the UserControl, that breaks the binding when used:

DescriptorTwo.xaml

<UserControl
x:Class="EmbedContent.UserControls.DescriptorTwo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptorTwo">
<UserControl.Template>
    <ControlTemplate>
        <DockPanel>
            <TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptorTwo}" />
            <ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptorTwo}" />
        </DockPanel>
    </ControlTemplate>
</UserControl.Template>

DescriptorTwo.xaml.cs

namespace EmbedContent.UserControls
{
    public partial class DescriptorTwo : UserControl
    {

        #region Ctor
        public DescriptorTwo()
        {
            InitializeComponent();
        }
        #endregion


        #region Dependency-Properties
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(DescriptorTwo), new PropertyMetadata("Descriptor's default Text"));
        #endregion
    }
}

And here an example of how both are called from another UserControl

Example.xaml

<UserControl
x:Class="EmbedContent.UserControls.EmbedContentExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
x:Name="parent">
<StackPanel>
    <Label>
        <!--  Binding to parent works  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
    </Label>
    <uc:Descriptor Text="description: ">
        <!--  Binding to parent works  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
    </uc:Descriptor>
    <uc:DescriptorTwo Text="description: ">
        <!--  Binding to parent fails  -->
        <TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
    </uc:DescriptorTwo>
</StackPanel>

and the codebehind

Example.xaml.cs

namespace EmbedContent.UserControls
{
    public partial class EmbedContentExample : UserControl
    {
        public EmbedContentExample()
        {
            InitializeComponent();
        }

        public string MyText
        {
            get { return (string)GetValue(MyTextProperty); }
            set { SetValue(MyTextProperty, value); }
        }

        public static readonly DependencyProperty MyTextProperty =
            DependencyProperty.Register("MyText", typeof(string), typeof(EmbedContentExample), new PropertyMetadata("EmbedContentExample's MyText"));
    }
}
negatic
  • 33
  • 8
0

Answer, including clarifying your decision: I finally found a solution.

Think about the problem a little deeper.
Binding a property of an element is part of the functionality of the element itself, not of its container.
Therefore, if you change the DataContext of an element, the default bindings are interpreted relative to it, not the DataContext of its container.
For this reason, you should not assign the DataContext inside a UserControl, as the default bindings behavior will change in a way that is unexpected for the user (programmer).

Now think about how ElementName bindings work.
For example, in the XAML that creates a new class from UserControl, you have defined named elements.
And then create multiple instances of UserControl.
In this case, if all elements with the same names were in the same visual tree, this would create a conflict, since it is not known which of them is being accessed.
(In this case, the "visual tree" is an oversimplification. It actually has to do with the system for registering the names of the UI elements).
To avoid such conflicts, when using XAML for ANY element (not only UserControl), its own local visual tree is created that is not associated with the main visual tree.

BUT! I already wrote above, Bindings are part of the functionality of the element itself.
And therefore, the binding of a property of type ElementName will search for an element by name not higher in the container, but in the UserControl itself.

The same goes for Templates.
Therefore, the names of elements inside Templates are not visible outside of it.

Default elements are not implemented as UserControl, but as Custom Control.
In this case, there is a separate Sharp class with element logic.
And a separate default Template for this class.
In this implementation, since no XAML is used to create the element, the element does not have its own internal local visual tree.
And all its bindings (of the ElementName type) are interpreted in relation to the general visual tree.

To some extent, you partially implemented this in the Descriptor: ContentControl class.
Only instead of regregistering the default Template for the type, you set it up in the XAML App.
This implementation will work for an example, but in many cases it can create other problems.
Therefore, it is better to use the "standard" Custom Control for which the theme is created and the default Template is registered.

In the case of DescriptorTwo, you specify x: Class = "EmbedContent.UserControls.DescriptorTwo" and this automatically creates its own local visual tree, relative to which the ElementName bindings will work.

Slightly modified example for the solution you found.

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

namespace EmbedContent.CustomControls
{
    public class Descriptor : ContentControl
    {

        #region Registering a default template
        static Descriptor()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Descriptor), new FrameworkPropertyMetadata(typeof(Descriptor)));
        }
        #endregion


        #region Ctor
        public Descriptor() : base() { }
        #endregion

        #region Dependency-Properties
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(nameof(Text), typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
        #endregion
    }
}

Theme with default templates - file Themes/Generic.xaml:

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


    <Style TargetType="{x:Type customcontrols:Descriptor}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
                    <StackPanel>
                        <TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
                        <TextBlock Text="{TemplateBinding Text}"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

DescriptorTwo.xml:

<UserControl x:Class="EmbedContent.UserControls.DescriptorTwo"
             x:Name="PART_Main"
             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:EmbedContent.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <StackPanel>
        <TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
        <TextBlock Text="{Binding Text, ElementName=PART_Main}"/>
    </StackPanel>
</UserControl>

Examle Window:

<Window x:Class="EmbedContent.ExampleWindow"
        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:EmbedContent"
        xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
        xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
        mc:Ignorable="d"
        Title="ExampleWindow" Height="450" Width="800">
    <StackPanel>
        <TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
        <Border Background="LightBlue" Margin="20" Padding="10"
                BorderBrush="Blue" BorderThickness="2">
            <customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}"/>
        </Border>
        <Border Background="LightGreen" Margin="20" Padding="10"
                BorderBrush="Green" BorderThickness="2">
        <usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}"/>
        </Border>
    </StackPanel>
</Window>

Result:
Screenshot of the Window showing the obtained result

enter image description here

As you can see in the XAML Designer and when launched at runtime, such bindings (in the UserControl) also work differently, which causes additional problems.
Therefore, the main purpose of the UserControl is to represent the Data coming through the DataContext.

Also consider the moment with the assignment of Content.
In UserControl in XAML, you set a value for this property.
And assigning it when used does not complement the presentation, but replace it.
In CustonControl, you can explicitly specify in the Template where to insert additional content.

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


    <Style TargetType="{x:Type customcontrols:Descriptor}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
                    <StackPanel>
                        <TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
                        <TextBlock Text="{TemplateBinding Text}"/>
                        <ContentPresenter Content="{TemplateBinding Content}"
                                          ContentTemplate="{TemplateBinding ContentTemplate}"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
<Window x:Class="EmbedContent.ExampleWindow"
        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:EmbedContent"
        xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
        xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
        mc:Ignorable="d"
        Title="ExampleWindow" Height="250" Width="400">
    <StackPanel>
        <TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
        <Border Background="LightBlue" Margin="20" Padding="10"
                BorderBrush="Blue" BorderThickness="2">
            <customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}">
                <TextBlock  Text="{Binding Text, ElementName=tblock}"/>
            </customcontrols:Descriptor>
        </Border>
        <Border Background="LightGreen" Margin="20" Padding="10"
                BorderBrush="Green" BorderThickness="2">
            <usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}">
                <TextBlock  Text="{Binding Text, ElementName=tblock}"/>
            </usercontrols:DescriptorTwo>
        </Border>
    </StackPanel>
</Window>

Result:
Screenshot of the Window showing the obtained result

EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • thx for the clarification. This deepens my understanding. Now i just need to look deeper into Custom Controls since i didn't like that i needed to add the style to App.xml or a resource dictionary independent from my real Control. I already accepted Meysam Asadi's answer, because he put so much effort into it but thx a lot for your explanation. – negatic Jul 10 '21 at 14:55
  • I have completed the answer. Read this supplement, it will be useful to you too. – EldHasp Jul 10 '21 at 15:23
  • yes you certainly did finish the answer. Now i have no choice but to accept this as the correct answer even tho it pains me a bit to have to change it. This is exactly what i was looking for. It doesn't just explains everything i didn't understand it even improved the answer i found myself by using Themes/Generic.xaml and explains that this is actually what a custom control is. I did hear about custom controls before but did not understand till today. Thx for your answer :) – negatic Jul 10 '21 at 15:35