1

I have a custom control, Cake, that contains two DependencyProperties named Slice and Filling. How do I create a style that gives me access to Slice, but also lets me design slice?

<Style TargetType={x:Type local:Cake}>
    //I don't like setting DataContext Here
    <Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource Self}}/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType={x:Type local:Cake}>
                <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">                        

                    //This is how I display a slice
                    <ContentPresenter Content={Binding Slice}/>

                    //This is how cake decorations are displayed
                    <ItemsPresenter/> 

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Slice">
        <Setter.Value>

            //Design Slice Here - it's easy to override when I want  
            <Slice Filling={Binding Filling}> // it's just in a setter.

        </Setter.Value>
    </Setter>
    <Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource Self}}/>
</Style>

Options I've tried:

  • I can't use a UserControl, because I want to allow named content, which apparently doesn't work with User Controls. See Here.

  • I don't like the example above because I have to set the DataContext of the Cake container to self, meaning a user can't use the DataContext for their bindings.

  • I can't bind the Filling property using RelativeSource, because with several cakes, the Style wouldn't know which one was the correct parent. See Here.

  • I could replace the Content Presenter directly with a Slice Element, but because it is in a template, I loose access to the Slice Anywhere outside the Template. While I could probably typecast my way down the visualTree to the slice, this feels a maintenance nightmare.

Access Slice from Cake Directly, but be able to edit it in xaml

I basically want each cake to have a slice, and to be able to set it using

<Cake.Slice>
    <DockPanel>
       <Rectangle Background= “Blue”/>
       <Rectangle Background= “Blue”/>
       <Rectangle Background=“{Binding Filling}”/>
    </DockPanel>
</Cake.Slice>

while also giving it a default look.

EDIT: Apparently my style DOES work, provided that I reference the Cake.dll as opposed to the Cake project. Why would that be?

bwall
  • 984
  • 8
  • 22

1 Answers1

1

This won't be exactly what you need, but I hope it will give you pointers how to achieve that.

First, you don't need to set DataContext to the control itself, you can bind to properties on the Cake control (Filling and Slice) from Cake's ControlTemplate using {TemplateBinding Slice}, which is just a shortcut for {Binding Slice, RelativeSource={RelativeSource TemplatedParent}} (so you can use one or the other).

This will be a simplified version of your control, as I don't know which items the ItemPresenter in your ControlTemplate should present, or what is the Type of your Slice and Filling properties. In this example Filling is SolidColorBrush, and Slice is a Style. That style is applied to a ContentControl within Cake's ControlTemplate, so you can have predefined styles for slices, and apply the filling of your choice (if your Slice property has different purpose, you can introduce another property called SliceStyle for example).

Cake control:

public class Cake : Control
{
    static Cake()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(Cake),
            new FrameworkPropertyMetadata(typeof(Cake)));
    }

    public SolidColorBrush Filling
    {
        get { return (SolidColorBrush)GetValue(FillingProperty); }
        set { SetValue(FillingProperty, value); }
    }

    public static readonly DependencyProperty FillingProperty =
        DependencyProperty.Register(
            "Filling",
            typeof(SolidColorBrush),
            typeof(Cake),
            new PropertyMetadata(Brushes.Transparent));

    public Style Slice
    {
        get { return (Style)GetValue(SliceProperty); }
        set { SetValue(SliceProperty, value); }
    }

    public static readonly DependencyProperty SliceProperty =
        DependencyProperty.Register(
            "Slice",
            typeof(Style),
            typeof(Cake),
            new PropertyMetadata(null));
}

Default Style (Generic.xaml):

<Style TargetType="{x:Type local:Cake}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:Cake}">
                <ContentControl Width="{TemplateBinding Width}"
                                Height="{TemplateBinding Height}"
                                Style="{TemplateBinding Slice}"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Different slice styles (these are setting ControlTemplate for ContentControl, so you will not hit the issue number 3 in your question):

<Window.Resources>
    <Style x:Key="TwoLayeredSlice" TargetType="{x:Type ContentControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Rectangle Fill="{Binding Filling,
                            RelativeSource={RelativeSource AncestorType={x:Type local:Cake}}}"/>
                        <Rectangle Fill="Brown"
                                   Grid.Row="1"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="FourLayeredSlice" TargetType="{x:Type ContentControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Rectangle Fill="{Binding Filling,
                            RelativeSource={RelativeSource AncestorType={x:Type local:Cake}}}"/>
                        <Rectangle Fill="Brown"
                                   Grid.Row="1"/>
                        <Rectangle Fill="{Binding Filling,
                            RelativeSource={RelativeSource AncestorType={x:Type local:Cake}}}"
                                   Grid.Row="2"/>
                        <Rectangle Fill="Brown"
                                   Grid.Row="3"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

And the control in use:

<Grid Background="Gray">
    <local:Cake Width="200"
                Height="100"
                HorizontalAlignment="Left"
                Filling="Gold"
                Slice="{StaticResource TwoLayeredSlice}"/>
    <local:Cake Width="200"
                Height="100"
                HorizontalAlignment="Center"
                Filling="Pink"
                Slice="{StaticResource FourLayeredSlice}"/>
    <local:Cake Width="200"
                Height="100"
                HorizontalAlignment="Right"
                Filling="Blue"
                Slice="{StaticResource FourLayeredSlice}"/>
</Grid>

Cakes

Bon appetit!

djomlastic
  • 422
  • 2
  • 6
  • While this still doesn't give me access to the slice directly, It does a **fantastic** job of allowing me to design it - thanks for finding a way to use RelativeSource! – bwall Nov 21 '17 at 16:10