HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Composable custom control that supports binding and property inheritance

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
controlcomposablesupportsinheritancecustompropertythatbindingand

Problem

This is an experiment to investigate how to wire up binding and inheritance in a composable, custom control complex.

Custom Control Structure

There is a custom parent (TestParent) with multiple custom child controls plugged into a ObservableCollection, DP on the parent. The Parent derives from Label and the children from FrameworkElement and the Label Content is updated by a Refresh method on the parent, based on property change notifications (just for experimental purposes).

Composability

The ObservableCollection DP on the parent control provides a site for attaching the child elements. This is supported by some glue logic to wire up the Logical relationships, between parent and child, that are required to support binding and implicit inheritance. The ObservableCollection needs to be initialised in the parent, instance constructor in order to provide a unique collection for each instance.

Property Inheritance

The parent has an Attached Property (AttachedString) that is inherited by the child elements. On the parent and on one of the child elements, this property is bound to a ComboBox. As per normal inheritance behaviour, when the value of the AP on the parent is changed, the unbound children will dynamically inherit the new value. The child class also has a property that does not inherit from the parent.

Test Page

The View includes of some ComboBox elements to provide changeable sources to test binding to the custom control complex at parent and child levels. The aim is to check that binding will work at both levels and that an attached property on the parent can be implicitly inherited onto its children.

`



Name
Command
CommandParameter






Parent String




Child String



Solution


  • You can drop equality checks in OnPropertyChanged since PropertyChanged callback only triggers when dependency property actually changes.



  • Your properties can use a better naming. AttachedString doesn't tell me anything. I can already see that the property returns string and I can see that it is an attached property. What I do not know is its purpose. Use property name to tell me that.



-
Your class wont work if I set MyItems from code. This is counter-intuitive. Either make the setter private or make it work.

-
You are leaking event handlers. Which might not be an issue for your current use case, but it can quickly become one, once stuff gets serious. Either use weak events or unsubscribe manually.

-
Label is a really poor choice for a base class. There are two main approaches when it comes to controls, that host multiple children:

-
Panel approach. Panels are responsible for arranging Visuals in available space.

-
ItemsControl approach. ItemsControl is responsible for assigning templates to arbitrary items and then hosting those templates in specified Panel.

Note, that in neither of those approaches parent is responsible for actually rendering children. You are having so much trouble updating your parent precisely because you've neglected this principle.

The problem is that I do not know how many wheels you want to reinvent. The most straightforward way to implement the behavior you want is to declare an attached property:

static class Attached
{
    public static readonly DependencyProperty StringProperty = DependencyProperty.RegisterAttached(
        "String", typeof (string), typeof (Attached), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.Inherits));

    public static void SetString(DependencyObject element, string value)
    {
        element.SetValue(StringProperty, value);
    }

    public static string GetString(DependencyObject element)
    {
        return (string) element.GetValue(StringProperty);
    }
}


...and that's it. The rest can be implemented using regular controls and bindings. For example:


    
        
            
        
    
    
    
    


From here you can take it into different directions depending on how deep you want to go.

  • You can define a special Label template, that would automatically change depending on the value of your attached property.



  • You can extend Label class and write a custom control (for child elements), which would update label's content depending on the values of some other custom properties, including attached ones.



  • You can extend Control class and write a custom template for it.



  • You can extend FrameworkElement class, and write your own rendering logic on low level using DrawingContext.



  • Etc.



I don't think you are going to need a special TestPanel for any of those options, simple StackPanel (or any other panel), should work just fine. But it is hard to tell for sure since your code is rather hypothetical-ish. I do not know the actual use case or an actual problem you are trying to solve (if any).

Code Snippets

static class Attached
{
    public static readonly DependencyProperty StringProperty = DependencyProperty.RegisterAttached(
        "String", typeof (string), typeof (Attached), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.Inherits));

    public static void SetString(DependencyObject element, string value)
    {
        element.SetValue(StringProperty, value);
    }

    public static string GetString(DependencyObject element)
    {
        return (string) element.GetValue(StringProperty);
    }
}
<StackPanel Orientation="Vertical" 
            Attached.String="{Binding SelectedValue, ElementName=ParentString}">
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Setter Property="Content" 
                    Value="{Binding (Attached.String), RelativeSource={RelativeSource Self}}"/>
        </Style>
    </StackPanel.Resources>
    <Label Content="{Binding SelectedValue, ElementName=UnattachedChildString}"/>
    <Label Attached.String="{Binding SelectedValue, ElementName=ChildString}"/>
    <Label />
</StackPanel>

Context

StackExchange Code Review Q#150477, answer score: 2

Revisions (0)

No revisions yet.