Marcel
Marcel

Reputation: 1074

WPF Composition over inheritance for dependency properties in my custom controls library

in short: I don't want to repeat myself by implementing a dependency property HighlightBrush each time I write a custom control.

Here are 2 examples of nasty code duplication:

public class MyButton : Button
{
    public static readonly DependencyProperty HighlightBrushProperty = DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(MyButton), new PropertyMetadata(default(Brush)));

    public Brush HighlightBrush
    {
        get { return (Brush) GetValue(HighlightBrushProperty); }
        set { SetValue(HighlightBrushProperty, value); }
    }
}
public class MyTextBox : TextBox
{
    public static readonly DependencyProperty HighlightBrushProperty = DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(MyTextBox), new PropertyMetadata(default(Brush)));

    public Brush HighlightBrush
    {
        get { return (Brush)GetValue(HighlightBrushProperty); }
        set { SetValue(HighlightBrushProperty, value); }
    }
}

For the sake of completeness, usage of HighlightBrush inside the Generic.xaml:

<Style TargetType="{x:Type custom:MyButton}">
    <Setter Property="Cursor" Value="Hand" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type custom:MyButton}">
                <Border x:Name="Border" Background="{TemplateBinding Background}">
                    <ContentPresenter />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="Border" Property="Background" Value="{Binding HighlightBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="{x:Type custom:MyTextBox}">
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type custom:MyTextBox}">
                <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <ScrollViewer x:Name="PART_ContentHost" />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsFocused" Value="True">
                        <Setter Property="BorderBrush" Value="{Binding HighlightBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Question: How can I avoid code duplication on dependency properties? I cannot introduce another base class because there's no multiple inheritance. I cannot use composition because the dependency properties need the concrete type to be created with. Maybe attached dependency properties help but how interact with them inside control class and XAML?

(I guess TextBlock.Text and TextBox.Text is very similar to my problem.)

Upvotes: 2

Views: 429

Answers (1)

heltonbiker
heltonbiker

Reputation: 27605

You can use AttachedProperties and interact with them in XAML, it just requires a bit more work and you getting used with the syntax.

For example, I wanted arbitrary control types to have a Geometria (Geometry) property, so I created an AttachedProperty class to contain it (names in Portuguese):

public static class PropriedadeAnexada
{
    public static readonly DependencyProperty GeometriaProperty
        = DependencyProperty.RegisterAttached(
            "Geometria",
            typeof(Geometry),
            typeof(PropriedadeAnexada),
            new FrameworkPropertyMetadata(
                default(Geometry),
                FrameworkPropertyMetadataOptions.AffectsRender
                | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
            )
        );

    public static Geometry GetGeometria (DependencyObject element)
    {
        return (Geometry)element.GetValue(GeometriaProperty);
    }

    public static void SetGeometria (DependencyObject element, Geometry value)
    {
        element.SetValue(GeometriaProperty, value);
    }
}

Then, in XAML, I set this property like this:

        <Button 
            controls:PropriedadeAnexada.Geometria="{StaticResource ÍconeNovoExame}"             
            Style="{StaticResource BotãoGeometria}"
        />

Note that here I only make this property available in my control by setting it, and access it in my style, which in turn uses a Template like this:

<ControlTemplate 
    x:Key="ControleGeometriaTemplate"
    TargetType="{x:Type Control}"
>
    <Grid x:Name="root">
            <Path
                x:Name="ícone"
                Data="{Binding Path=(local:PropriedadeAnexada.Geometria), RelativeSource={RelativeSource TemplatedParent}}"
            />
    </Grid>
</ControlTemplate>

Upvotes: 2

Related Questions