116

I have a set of controls with attached commands and logic that are constantly reused in the same way. I decided to create a user control that holds all the common controls and logic.

However I also need the control to be able to hold content that can be named. I tried the following:

<UserControl.ContentTemplate>
    <DataTemplate>
        <Button>a reused button</Button>
        <ContentPresenter Content="{TemplateBinding Content}"/>
        <Button>a reused button</Button>
    </DataTemplate>
</UserControl.ContentTemplate>

However it seems any content placed inside the user control cannot be named. For example if I use the control in the following way:

<lib:UserControl1>
     <Button Name="buttonName">content</Button>
</lib:UserControl1>

I receive the following error:

Cannot set Name attribute value 'buttonName' on element 'Button'. 'Button' is under the scope of element 'UserControl1', which already had a name registered when it was defined in another scope.

If I remove the buttonName, then it compiles, however I need to be able to name the content. How can I achieve this?

H.B.
  • 166,899
  • 29
  • 327
  • 400
Ryan
  • 1,621
  • 2
  • 13
  • 14
  • This is a coincidence. I was just about to ask this question! I have the same problem. Factoring out common UI pattern into a UserControl, but wanting to refer to the content UI by name. – mackenir Apr 15 '09 at 16:11
  • 2
    Why don't you use the ResourceDictionary way? Define the DataTemplate in it. Or use the BasedOn keyword to inherit the control. Just some paths I'd follow before doing code-behind UI in WPF... – Louis Kottmann Jul 22 '11 at 22:37
  • 3
    This guy found a [solution](http://blog.bluecog.co.nz/archives/2007/08/27/wpf-cannot-set-name-attribute/) involving getting rid of his custom control's XAML file, and building the custom control's UI programmatically. This [blog post](http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!2130.entry?wa=wsignin1.0&sa=752255111) has more to say on the subject. – mackenir Apr 15 '09 at 16:43

11 Answers11

57

The answer is to not use a UserControl to do it.

Create a class that extends ContentControl

public class MyFunkyControl : ContentControl
{
    public static readonly DependencyProperty HeadingProperty =
        DependencyProperty.Register("Heading", typeof(string),
        typeof(MyFunkyControl), new PropertyMetadata(HeadingChanged));

    private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyFunkyControl) d).Heading = e.NewValue as string;
    }

    public string Heading { get; set; }
}

then use a style to specify the contents

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyControl">
                <Grid>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and finally - use it

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>
Examath
  • 176
  • 9
Sybrand
  • 766
  • 1
  • 6
  • 6
  • 3
    I found this to actually the most comfortable solution since you can flesh out the ControlTemplate in an ordinary UserControl using the designer and that transform it into a style with associated control template. – Oliver Weichhold Aug 25 '10 at 12:04
  • 5
    Hmm, it is a bit strange it works for you, because I tried to apply this approach and in my case I still get this infamous error. – greenoldman Apr 23 '11 at 14:42
  • 9
    @greenoldman @Badiboy I think I know why it didn't work for you. you probably just changed an existing code from `UserControl` to inherit `ContentControl`. To solve, simply add new Class (**not** XAML with CS). And then it will (hopefully) work. if you like, I've created a small [VS2010 solution](http://www.mediafire.com/download/vxm1a1d3uxurc8e/ControlWithNamedContentTest1000.rar) – itsho Oct 23 '13 at 13:03
  • 2
    Many years later and I wish I could upvote this again :) – Drew Noakes Mar 20 '17 at 17:42
  • 1
    Is the ` – dx_over_dt Jul 16 '18 at 21:46
  • I've encountered this issue on a very mature UserControl, and converting it to a ContentControl is not an option. What exactly is special about `ContentControl.Content` that causes WPF to preserve the original logical tree position of controls assigned to it? Can I replicate that on another property? – Artfunkel Nov 06 '18 at 14:57
  • 1
    I did exactly this and I still get the error... it DOESN'T work – baye dbest Dec 29 '18 at 09:28
  • 1
    @itsho Tested your example and works! You can also bind your template programmatically at control creation using: `this.Template = Application.Current.Resources["MySpecialTemplate"] as ControlTemplate;` – Adam Calvet Bohl Jan 22 '19 at 06:04
  • 3
    I guess `HeadingContainer` and `MyFunkyContainer` are meant to be `MyFunkyControl`?! – Martin Schneider Oct 01 '19 at 08:38
  • I attempted this, but I don't know what "HeadingContainer" is, and Visual Studio does not seem to know either. Is there something else I am supposed to do? I don't know much about the inner-workings of WPF. – XSapien Mar 24 '20 at 02:04
  • Why was this answer upvoted? The code is incomplete and even with my best guess of what was expected, the problem still exists. – prw56 Apr 06 '20 at 19:28
23

It seems this is not possible when XAML is used. Custom controls seem to be a overkill when I actually have all the controls I need, but just need to group them together with a small bit of logic and allow named content.

The solution on JD's blog as mackenir suggests, seems to have the best compromise. A way to extend JD's solution to allow controls to still be defined in XAML could be as follows:

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        var grid = new Grid();
        var content = new ContentPresenter
                          {
                              Content = Content
                          };

        var userControl = new UserControlDefinedInXAML();
        userControl.aStackPanel.Children.Add(content);

        grid.Children.Add(userControl);
        Content = grid;           
    }

In my example above I have created a user control called UserControlDefinedInXAML which is define like any normal user controls using XAML. In my UserControlDefinedInXAML I have a StackPanel called aStackPanel within which I want my named content to appear.

Ryan
  • 1,621
  • 2
  • 13
  • 14
  • I've been finding that I get data binding problems when using this mechanism of re-parenting the content. The data-binding seems set up correctly, but the initial populating of the controls from the data source doesn't work properly. I think the problem is limited to controls that are not the direct child of the content presenter. – mackenir Apr 24 '09 at 08:36
  • Not had a chance to experiment with this since, but I've not had any problems databinding to either the controls defined in the UserControlDefinedInXAML (from above example) or the controls added to the ContentPresenter so far. I have been databinding through code only though (not XAML - not sure if that makes a difference). – Ryan Apr 27 '09 at 16:22
  • It seems it DOES make a difference. I just tried using XAML for my data bindings in the case you described and it does not work. But if I set it in code, it does work! – Ryan Apr 29 '09 at 22:01
  • thank you very much! after 1 day of research, finally it helped! – Farkhod Oct 27 '21 at 08:11
4

Another alternative I've used is to just set the Name property in the Loaded event.

In my case, I had a rather complex control which I didn't want to create in the code-behind, and it looked for an optional control with a specific name for certain behavior, and since I noticed I could set the name in a DataTemplate I figured I could do it in the Loaded event too.

private void Button_Loaded(object sender, RoutedEventArgs e)
{
    Button b = sender as Button;
    b.Name = "buttonName";
}
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • 2
    If you do this, then bindings using the name will not work... unless you set the bindings in code behind. – Tower Jul 04 '12 at 14:11
  • This seems to be the cleanest solution. Relative to other solutions, this one does not come with side effects. But you get this clean, not for free. You are paying by having to write a little more code outside the XAML file. But that code is not so big.. unless you are trying to set configure binding from there (because the code to set binding is not short and looks a little too complicated for a thing that you may do a lot of times). For this, I wrote the extension method: (see next comment) – gil123 May 09 '23 at 23:39
  • ```public static void SetBinding(this FrameworkElement element, DependencyProperty elementDependencyProperty, FrameworkElement sourceElement, DependencyProperty sourceElementDependencyProperty) { element.SetBinding(elementDependencyProperty, new Binding(sourceElementDependencyProperty.Name) { Source = sourceElement }); }``` You can use it like this: ```void ChildToggle_OnLoaded(object sender, RoutedEventArgs e) { toggleSwitch = sender as ToggleSwitch; toggleSwitch.SetBinding(ToggleSwitch.IsEnabledProperty, mainToggle, ToggleSwitch.IsOnProperty); }``` – gil123 May 09 '23 at 23:41
  • I added here my version of a workaround that is based on using the `Loading` event – gil123 May 12 '23 at 19:30
3

Sometimes you might just need to reference the element from C#. Depending on the use case, you can then set an x:Uid instead of an x:Name and access the elements by calling a Uid finder method like Get object by its Uid in WPF.

Community
  • 1
  • 1
Dani
  • 2,602
  • 2
  • 23
  • 27
1

You can use this helper for set name inside the user control:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
namespace UI.Helpers
{
    public class UserControlNameHelper
    {
        public static string GetName(DependencyObject d)
        {
            return (string)d.GetValue(UserControlNameHelper.NameProperty);
        }

        public static void SetName(DependencyObject d, string val)
        {
            d.SetValue(UserControlNameHelper.NameProperty, val);
        }

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.RegisterAttached("Name",
                typeof(string),
                typeof(UserControlNameHelper),
                new FrameworkPropertyMetadata("",
                    FrameworkPropertyMetadataOptions.None,
                    (d, e) =>
                    {
                        if (!string.IsNullOrEmpty((string)e.NewValue))
                        {
                            string[] names = e.NewValue.ToString().Split(new char[] { ',' });

                            if (d is FrameworkElement)
                            {
                                ((FrameworkElement)d).Name = names[0];
                                Type t = Type.GetType(names[1]);
                                if (t == null)
                                    return;
                                var parent = FindVisualParent(d, t);
                                if (parent == null)
                                    return;
                                var p = parent.GetType().GetProperty(names[0], BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
                                p.SetValue(parent, d, null);
                            }
                        }
                    }));

        public static DependencyObject FindVisualParent(DependencyObject child, Type t)
        {
            // get parent item
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            // we’ve reached the end of the tree
            if (parentObject == null)
            {
                var p = ((FrameworkElement)child).Parent;
                if (p == null)
                    return null;
                parentObject = p;
            }

            // check if the parent matches the type we’re looking for
            DependencyObject parent = parentObject.GetType() == t ? parentObject : null;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                // use recursion to proceed with next level
                return FindVisualParent(parentObject, t);
            }
        }
    }
}

and your Window or Control Code Behind set you control by Property:

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

    }

    public Button BtnOK { get; set; }
}

your window xaml:

    <Window x:Class="user_Control_Name.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:test="clr-namespace:user_Control_Name"
            xmlns:helper="clr-namespace:UI.Helpers" x:Name="mainWindow"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <test:TestUserControl>
                <Button helper:UserControlNameHelper.Name="BtnOK,user_Control_Name.MainWindow"/>
            </test:TestUserControl>
            <TextBlock Text="{Binding ElementName=mainWindow,Path=BtnOK.Name}"/>
        </Grid>
    </Window>

UserControlNameHelper get your control name and your Class name for set Control to Property.

Ali Yousefi
  • 2,355
  • 2
  • 32
  • 47
0

I've chosen to create an extra property for each element I need to get:

    public FrameworkElement First
    {
        get
        {
            if (Controls.Count > 0)
            {
                return Controls[0];
            }
            return null;
        }
    }

This enables me to access the child elements in XAML:

<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>
aliceraunsbaek
  • 625
  • 7
  • 18
0
<Popup>
    <TextBox Loaded="BlahTextBox_Loaded" />
</Popup>

Code behind:

public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
    BlahTextBox = sender as TextBox;
}

The real solution would be for Microsoft to fix this issue, as well as all the others with broken visual trees etc. Hypothetically speaking.

  • I added my version of workaround that is based on this idea but it does it much more smarter. you will not need to create a method per control. only properties. See here my answer – gil123 May 12 '23 at 19:32
0

Yet another workaround: reference the element as RelativeSource.

voccoeisuoi
  • 325
  • 4
  • 11
0

I had the same problem using a TabControl when placing a bunch of named controls into.

My workaround was to use a control template which contains all my controls to be shown in a tab page. Inside the template you can use the Name property and also data bind to properties of the named control from other controls at least inside the same template.

As Content of the TabItem Control, use a simple Control and set the ControlTemplate accordingly:

<Control Template="{StaticResource MyControlTemplate}"/>

Accessing those named control inside the template from code behind you would need to use the visual tree.

0

I ran into this problem and found a workaround that lets you design custom controls using Xaml. Its still has a bit of a hack, but one that solved all of my problems without any obvious compromises.

Basically, you do everything the way you normally would with the xaml, but you also include some of the header declarations on the control template itself and Base64 encode that template to be loaded in the code constructor. Not shown in this Xaml excerpt, but the namespace my full Xaml used is actually targeting a XamlTemplates instead of the Controls namespace. This was on purpose because the "Release" build moves that developmental Debug reference out of the way from my production controls namespace. More on that below.

<ControlTemplate TargetType="{x:Type TabControl}" 
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="templateRoot" 
          ClipToBounds="True" 
          SnapsToDevicePixels="True" 
          Background="Transparent"
          KeyboardNavigation.TabNavigation="Local">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="ColumnDefinition0"/>
            <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
            <RowDefinition x:Name="RowDefinition1" Height="*"/>
        </Grid.RowDefinitions>
        <TabPanel x:Name="HeaderPanel"
                  Panel.ZIndex="1"                          
                  Margin="{Binding MarginHeaderPanel, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  IsItemsHost="True"                          
                  KeyboardNavigation.TabIndex="2"/>
        <Border x:Name="blankregion" Panel.ZIndex="1" Margin="0" Padding="0" 
                Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}">
            <ContentPresenter x:Name="blankpresenter"                                      
                              KeyboardNavigation.TabIndex="1"    
                              Content="{Binding TabBlankSpaceContent, RelativeSource={RelativeSource AncestorType=TabControl}}"                                          
                              ContentSource="TabBlankSpaceContent" 
                              SnapsToDevicePixels="True"/>
        </Border>

        <Grid x:Name="ContentPanel">
            <Border 
                BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TabControl}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=TabControl}}"                       
                Background="{Binding SelectedItem.Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.DirectionalNavigation="Contained" 
                KeyboardNavigation.TabNavigation="Local"                                           
                CornerRadius="{Binding BorderRadius, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.TabIndex="3">
                <ContentControl x:Name="PART_SelectedContentHost" 
                                ContentTemplate="{Binding SelectedContentTemplate, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                Content="{Binding SelectedContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                ContentStringFormat="{Binding SelectedContentStringFormat, RelativeSource={RelativeSource AncestorType=TabControl}}" 
                                Margin="{Binding Padding, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                SnapsToDevicePixels="{Binding SnapsToDevicePixels, RelativeSource={RelativeSource AncestorType=TabControl}}"/>
            </Border>

        </Grid>
    </Grid>
    <ControlTemplate.Triggers>
        <!--Triggers were removed for clarity-->
    </ControlTemplate.Triggers>
</ControlTemplate>

I'll point out that the above XAML didn't name the control it derived from and everything within the template used relative lookups to bind its properties; even the custom ones.

On the C# side, I used the Base64 encoded version of the control template from my Xaml and directives to shuffle around the development/release versions of the controls. The theory being that my controls in the development space wouldn't run into the problem this topic is about, but would give me a way to test/develop them. The release DLL versions seem to be working really well and the controls built do have great design time support just like they did on the Debug/Development side.

#if DEBUG
namespace AgileBIM.Controls
{
    public class AgileTabControl : AgileBIM.XamlTemplates.AgileTabControlDesigner { }
}

namespace AgileBIM.XamlTemplates
#else
namespace AgileBIM.Controls
#endif
{
#if DEBUG    
    public partial class AgileTabControlDesigner : TabControl
#else
    public class AgileTabControl : TabControl
#endif
    {

        

#if DEBUG
        private static Type ThisControl = typeof(AgileTabControlDesigner);
#else
        private static Type ThisControl = typeof(AgileTabControl);
        private string Template64 = "Base64 encoded template removed for clarity"
#endif


#if DEBUG
        public AgileTabControlDesigner() { InitializeComponent(); }
#else
        public AgileTabControl()
        {
            string decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Template64));
            System.IO.StringReader sr = new System.IO.StringReader(decoded);
            System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr);
            ControlTemplate ct = (ControlTemplate)System.Windows.Markup.XamlReader.Load(xr);

            DefaultStyleKey = ThisControl;
            Template = ct;
        }
#endif

        public Thickness MarginHeaderPanel 
        {
            get { return (Thickness)GetValue(MarginHeaderPanelProperty); } 
            set { SetValue(MarginHeaderPanelProperty, value); } 
        }
        public static readonly DependencyProperty MarginHeaderPanelProperty =
            DependencyProperty.Register("MarginHeaderPanel", typeof(Thickness), ThisControl, new PropertyMetadata(new Thickness(0)));

        public CornerRadius BorderRadius 
        { 
            get { return (CornerRadius)GetValue(BorderRadiusProperty); } 
            set { SetValue(BorderRadiusProperty, value); }
        }
        public static readonly DependencyProperty BorderRadiusProperty =
            DependencyProperty.Register("BorderRadius", typeof(CornerRadius), ThisControl, new PropertyMetadata(new CornerRadius(0)));

        public object TabBlankSpaceContent 
        { 
            get { return (object)GetValue(TabBlankSpaceContentProperty); } 
            set { SetValue(TabBlankSpaceContentProperty, value); } 
        }
        public static readonly DependencyProperty TabBlankSpaceContentProperty =
            DependencyProperty.Register("TabBlankSpaceContent", typeof(object), ThisControl, new PropertyMetadata());
    }
}

The critical thing to remember before creating a "release" control DLL to be used in your primary application is to update your base64 encoded string with your latest and greatest version of its control template. This is because the Release build is completely detached from the original Xaml and entirely dependent on the encoded one.

The above control and others like it can be found on GitHub. Which is a library I am making intended to "unlock" many of the things I want to style that standard controls don't expose. That and adding some features that don't exist. For example, the above TabControl has an additional content property for utilizing the "unused" area of the tab headers.

Important Notes:

  • Basic styling gets lost using this method, but you get it all back if your styles for the Custom Control uses the BasedOn="{StaticResource {x:Type TabControl}}" mechanism.
  • I need to find time to research if this will cause any noteworthy memory leaks and whether I can do anything to combat them, if anyone has any thoughts on this let me know in the comments.
Bogatitus
  • 21
  • 1
  • 5
0

Based on Dani answer to use the Loaded event, I figured out a more clean workaround for the problem.

The idea is to use the Tag property (available in any element) with a combination of NameOfExtension that can be found here, and use the Loaded event.

First, add the NameOfExtension to your project:

namespace UI.Extensions
{
    public class NameOfExtension : MarkupExtension
    {
        private readonly PropertyPath _propertyPath;

        public NameOfExtension(Binding binding)
        {
            _propertyPath = binding.Path;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var indexOfLastVariableName = _propertyPath.Path.LastIndexOf('.');
            return _propertyPath.Path.Substring(indexOfLastVariableName + 1);
        }
    }
}

In your main user control (or window. same example valid if it is window)

<UserControl x:Class="App.MainControl"
             ....
             xmlns:extensions="clr-namespace:UI.Extensions"
             xmlns:controls="clr-namespace:UI.Controls"
             ...
             Name="_this"
             ...
             >
</UserControl>

You need to declare usage of the namespace where the extension NameOfExtension is located by adding xmlns:extensions="clr-namespace:UI.Extensions", and set the name of the main element to be _this by Name="_this"

Note: xmlns:controls="clr-namespace:UI.Controls" is for using custom controls I own. In your case, it can be something different. In my case, the namespace contain a custom control with name ExpanderControl

Now, given that you have something like this:

<UserControl x:Class="App.MainControl"
             ....
             xmlns:extensions="clr-namespace:UI.Extensions"
             xmlns:controls="clr-namespace:UI.Controls"
             ...
             Name="_this"
             ...
             >

            <controls:ExpanderControl ExpandHeader="Some header">
                <StackPanel>
                    <CheckBox Name="MyCheckbox"/>
                    <CheckBox Name="MyCheckbox2"/>
                </StackPanel>
            </controls:ExpanderControl>
             
</UserControl>

You will need to change it to this:

<UserControl x:Class="App.MainControl"
             ....
             xmlns:extensions="clr-namespace:UI.Extensions"
             xmlns:controls="clr-namespace:UI.Controls"
             ...
             Name="_this"
             ...
             >

            <controls:ExpanderControl ExpandHeader="Some header">
                <StackPanel>
                    <CheckBox Loaded="LoadControlsProperties" Tag="{extensions:NameOf {Binding ElementName=_this, Path=MyCheckbox}}"/>
                    <CheckBox Loaded="LoadControlsProperties" Tag="{extensions:NameOf {Binding ElementName=_this, Path=MyCheckbox2}}"/>
                </StackPanel>
            </controls:ExpanderControl>
             
</UserControl>

And in the class of MainControl, you will need:

public partial class MainControl : UserControl
{

    // List of control properties
    public CheckBox MyCheckbox { get; private set; }
    public CheckBox MyCheckbox2 { get; private set; }
    
    ...
    ...

    // Call this method from "Loaded" of any control that with Tag like we did in the example
    void LoadControlsProperties(object sender, RoutedEventArgs e)
    {
    
        // Add to here any control to load from here in the following way.
        // If the control is of another type, such as TextBlock, you will need to change as CheckBox to "as TextBlock" for example.
        // The part of ??= is very important here. do not write =. only ??=
        MyCheckbox ??= Load(nameof(MyCheckbox)) as CheckBox;
        MyCheckbox2 ??= Load(nameof(MyCheckbox2)) as CheckBox;

        object Load(string varName)
        {
            var frameworkElement = sender as FrameworkElement;
            if (frameworkElement?.Tag == null) return null;
            return varName == (string)frameworkElement.Tag ? sender : null;
        }
    }
}
gil123
  • 512
  • 6
  • 12
  • My solution is incredibly bad, and this is worse (but I never thought of passing a Binding to a MarkupExtension constructor -- I'm going to look into that). The solution is ***do not use a UserControl for this purpose***. This is not what UserControls are for. This is a general-purpose custom control. Inherit from Control and write an implicit style that sets ControlTemplate. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '23 at 17:30
  • In the end I did not did this solution. I realized that there is no reason to give name to elements.. I ended with another solution, that of using binding only. There was still an issue - binding does not work if I do it directly in the XAML. But it does work if doing it from the loaded event. So I wrote some extension method that simplifies the binding setup and what I did was just use it from a loaded event where I wanted. I can update my answer.. but then the answer may suggest that the OP is asking the wrong question, and maybe he should not even need to use NAMED properties (just binding) – gil123 May 15 '23 at 22:22
  • The problem with "do not use a UserControl for this purpose" solution is that I don't see an easy way to work with it with the XAML designer. Styles are broken and not working. No idea why. So I ended with what I did. – gil123 May 15 '23 at 23:01
  • I use this: `public static void ConfigureBinding(this object elementObj, DependencyProperty elementDependencyProperty, FrameworkElement sourceElement, Expression> pathExpr, IValueConverter converter = null) { ((FrameworkElement)elementObj).SetBinding(elementDependencyProperty, new Binding(elementDependencyProperty.Name) { Source = sourceElement, Path = new PropertyPath(NameOf(pathExpr)), Converter = converter, Mode = BindingMode.TwoWay });}`. example: ` sender.ConfigureBinding(VisibilityProperty, this, () => this.IsExpanded, new VisibleOrCollapsedBoolConverter());` – gil123 May 15 '23 at 23:04