0

I recentrly discovered "reusable controls" in WPF and I have a project where they seem to provide me with a solution to a problem I have.

Let me sketch the situation: I need to make several UI elements. All of them share a common base, a common style/layout/template let's say, and they also share some common logic.

Next to that, all of these elements have some element-specific stuff.

You could say that I have some kind of inheritance here, but then for both XAML and CS.

The way I wanted to solve this, was by making an outer reusable element, I made a small example. The common part Is the Title label and the border. The element-specific UI can then be inserted into UserContent.

The code looks something like this (although it's simplified for the sake of brevity and conciseness, I also have an eventhandler and a routed event in my actual application):

ReusableControl.xaml

<UserControl x:Class="StackOverflowQuestion4.ReusableControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Name="root">

    <Border BorderBrush="Black"
            BorderThickness="1"
            Width="400"
            Height="200">
        
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <Label Content="{Binding Title, ElementName=root}" 
                   Grid.Row="0"/>

            <ContentControl Content="{Binding UserContent, ElementName=root}"
                            Grid.Row="1"/>

        </Grid>
    </Border>

</UserControl>

ReusableControl.xaml.cs

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

namespace StackOverflowQuestion4
{
    public partial class ReusableControl : UserControl
    {
        public ReusableControl()
        {
            InitializeComponent();
        }

        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(ReusableControl), new PropertyMetadata(string.Empty));

        public object UserContent
        {
            get { return GetValue(UserContentProperty); }
            set { SetValue(UserContentProperty, value); }
        }

        public static readonly DependencyProperty UserContentProperty =
            DependencyProperty.Register("UserContent", typeof(object), typeof(ReusableControl), new PropertyMetadata(string.Empty));
    }
}

Lovely, I can now use my special control in other parts of my code, and I can insert whatever I want into the UserContent field.

MainWindow.xaml

<Window x:Class="StackOverflowQuestion4.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:StackOverflowQuestion4"
        mc:Ignorable="d"
        Title="MainWindow"
        SizeToContent="WidthAndHeight">
    
    <Grid Width="800"
          Height="600">
        
        <local:ReusableControl Title="Test">
            <local:ReusableControl.UserContent>
                <Rectangle Width="300"
                           Height="100"
                           Fill="Blue"/>
            </local:ReusableControl.UserContent>
        </local:ReusableControl>
        
    </Grid>
    
</Window>

This works great, but the problem arises when I start to name things. Simply adding a name to an element inside of my ReusableControl causes a compilation error.

<Rectangle Width="300"
           Height="100"
           Fill="Blue"
           Name="LolWhatAmIDoing"/>

I get the following error:

MC3093 - Cannot set Name attribute value 'LolWhatAmIDoing' on element 'Rectangle'. 'Rectangle' is under the scope of element 'ReusableControl', which already had a name registered when it was defined in another scope.

This seems like such a small issue, but I cannot find an easy solution to this problem.

I found this thread on the forum, but it does not really provide a solution. Since I'm pretty new to all of this, I also don't really get what the issue is, so apologies if I'm slow minded.

Should I move to CustomControls?

1 Answers1

1

What you show is a simple property assignment: you set the value of type Rectangle to the property ReusableControl.UserContent. It's important to understand that the Rectangle is not part of the visual tree at this point. It's a simple property value that is only accessible via the property and not via the visual tree.
This all happens in the scope of MainWindow.

But the Rectangle is not a member of this scope. The ReusableControl is adding it to its own visual subtree or scope by binding the value of ReusableControl.UserContent to a ContentControl. This is were the Rectangle exists i.e. is rendered in the visual tree.

It effectively doesn't exist in the scope of MainWindow. It effectively only exists inside the ReusableControl in the "shape" of a ContentControl. This means that the scope of ReusableControl is the only name scope where you can register a name for child elements. It's also the only scope where you can directly reference it (if it had been defined and registered in this scope).

If you understand this, then you understand that the Rectangle is currently trying to register a name in the wrong scope, a scope in which it doesn't exist.

As a consequence, you cannot directly refer to it in the scope of MainWindow. You would have to dig into the ContentTemplate of the UserControl (which is a ContentControl) in order to get the nested ContentControl that actually hosts the Rectangle.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you very much. That's a very clear answer to the issue at hand. Any suggestions on how I could make a UI element with user definable and namable contents? Ir is the solution to bind the contents of my Reusable control to aonther UseControl panel which defines element specific UI stuff? Or is it possible to make a template and apply it to the UserControl? – Elias Verstappe Dec 12 '22 at 12:31
  • Usually, when you want to make the content of a control customizable, you enable the client code to define a particular DataTemplate. If the customization is not about data presentation but general layout and behavior, you would usually expect the ControlTemplate to be overridden. The client code is free to define elements of choice as well as name them without any restriction. Just think about how you can customize the ListBox control. A Button is another simple content control (in terms of visual appearance) that you can learn from how to host arbitrary content. – BionicCode Dec 12 '22 at 14:09
  • If you are authoring a simple content host, all you have to do is to place a ContentPresenter inside the ControlTemplate and bind it to the host's content property (this is also how the e.g. Button is implemented). It's up to the user to define the actual content. If you want to support templating, let your control define a dependency property of type Template, for example named UserContentTemplate to follow your example. Then bind this property to the ContentPresenter.ContentTemplate property. – BionicCode Dec 12 '22 at 14:09