Question


1. Issue

As we know,Attached Property widely expands the property system in wpf.But all the examples which are familiar to us are almost Parent-Defined ones,such as DockPanel.Dock / Grid.Row and so on.But after checking the document in MSDN,I found there are some other usages of Attached Property:

From MSDN:Attached Properties Overview / How Attached Properties Are Used by the Owning Type

1.The type that defines the attached property is designed so that it can be the parent element of the elements that will set values for the attached property. The type then iterates its child objects through internal logic against some object tree structure, obtains the values, and acts on those values in some manner.(Parent-Defined)

2.The type that defines the attached property will be used as the child element for a variety of possible parent elements and content models.(Child-Defined)

3.The type that defines the attached property represents a service. Other types set values for the attached property. Then, when the element that set the property is evaluated in the context of the service, the attached property values are obtained through internal logic of the service class.(Use as a Common Service)


2. Trial

Since Attached Property Can be defined by user,I thoought maybe we can use "CallBackMethod" to handle it.So I have coded some trials to validate my idea (Part 4.Code):

1.Customize a child control ("Son") which defined a attached property named "CellBorderThicknessProperty" and use "PropertyChangedCallBack" to update the layout;

2.Create a parent control ("Father") which's template contains the child control.

3.Use the parent control in a Window and set the child.CellBorderThickness's value;


3. Problem

1.As you see,It's not a good way to expose the "Parent Type" in "Child Type",especially we won't how many Parents there would be...

2.This Trial didnt work well,'Cus when the "PropertyChangedCallBack" was fired,the Father's template was not applied yet!So,Father.SetBorderThickness() will do nothing!

Is there any example which has used the Child-Defined attached property?And how does it work?

I am so eager to know how MS developers do with the Child-Defined ones.

eg: what about ScrollViwer.VerticalScrollBarVisibility in WPFToolkit:DataGrid?


4. Code

Son Control (Child)

<Style TargetType={x:Type Son}>
   <Setter Property="Template">
     <Setter.Value>
       <ControlTemplate>
         <Border BorderThickness={TemplateBinding CellBorderThickness}>
           ...
         </Border>
       </ControlTemplate>
     <Setter.Value>
   </Setter>

 public class Son: Control
 {
      public static readonly DependencyProperty CellBorderThicknessProperty = DependencyProperty.RegisterAttached("CellBorderThickness", typeof(Thickness), typeof(Son), new FrameworkPropertyMetadata(new Thickness(0.2), FrameworkPropertyMetadataOptions.AffectsRender, CellBorderThicknessProperty_ChangedCallBack));

      public static void SetCellBorderThickness(UIElement obj, Thickness value)
      {
        obj.SetValue(Son.CellBorderThicknessProperty, value);
      }

      public static Thickness GetCellBorderThickness(UIElement obj)
      {
        return (Thickness)obj.GetValue(Son.CellBorderThicknessProperty);
      }

      public Thickness CellBorderThickness
      {
        //With this, CellBorderThickness can be used as a normal dependency property.
        get { return (Thickness)GetValue(CellBorderThicknessProperty); }
        set { SetValue(CellBorderThicknessProperty, value); }
      }

      static void CellBorderThicknessProperty_ChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
        if ((d as Father) != null)
        {
            // Try to update the Son's CellBorderThickness which is the child element of  the Father.
            d.SetBorderThickness(e.NewValue);
        }
      }
 }

Father Control (Parent)

 <Style TargetType={x:Type Father}>
   <Setter Property="Template">
     <Setter.Value>
       <ControlTemplate>
         <Border>
           <Son></Son>
         </Border>
       </ControlTemplate>
     <Setter.Value>
   </Setter>

 public class Father:Control
 {
   private Son childControl;

   public void override OnApplyTemplate()
   {
     childControl=(Son)GetTemplateChild("PART_ChildControl");//Here is a problem!!
   }

   public void SetBorderThickness(Thickness value)
   {
     if(childControl==null)
       childControl.CellBorderThickness=value;
   }
 }

Window

 <Window>
   <Grid>
     <Father Son.CellBorderThichness="5"></Father>
   </Grid>
 <Window>
Was it helpful?

Solution

I took your example as a basis, removed a bit too much, and got this example. I removed them to show a minimal example of work.

First, I removed CellBorderThickness property, as is already attached dependency property.

Son

// Removed
public Thickness CellBorderThickness
{
    get { return (Thickness)GetValue(CellBorderThicknessProperty); }
    set { SetValue(CellBorderThicknessProperty, value); }
}

In my father control I removed OnApplyTemplate(), and in function SetBorderThickness() use the opportunity of attached dependency properties to set value:

Father

// Removed
OnApplyTemplate() { ... }

// Add
Son.SetCellBorderThickness(childControl, value);

Below is a complete example. The structure of example:

enter image description here

XAML

Styles

Son

<Style TargetType="{x:Type SonNamespace:Son}">
    <Setter Property="Background" Value="Gainsboro" />
    <Setter Property="BorderBrush" Value="Black" />
    <Setter Property="Width" Value="100" />
    <Setter Property="Height" Value="100" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="HorizontalContentAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding SonNamespace:Son.CellBorderThickness}">

                    <ContentPresenter Content="I'am a Son" 
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Father

<Style TargetType="{x:Type FatherNamespace:Father}">
    <Setter Property="Background" Value="AliceBlue" />
    <Setter Property="BorderBrush" Value="Black" />
    <Setter Property="Width" Value="100" />
    <Setter Property="Height" Value="100" />
    <Setter Property="HorizontalAlignment" Value="Right" />
    <Setter Property="HorizontalContentAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding SonNamespace:Son.CellBorderThickness}">

                    <ContentPresenter Content="I'am a Father" 
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

MainWindow

<Grid>
    <SonNamespace:Son />

    <FatherNamespace:Father SonNamespace:Son.CellBorderThickness="6" />
</Grid>

Code

Son

public class Son : Control
{
    public static readonly DependencyProperty CellBorderThicknessProperty =
                                              DependencyProperty.RegisterAttached("CellBorderThickness",
                                              typeof(Thickness), typeof(Son),
                                              new FrameworkPropertyMetadata(new Thickness(2),
                                                                            FrameworkPropertyMetadataOptions.AffectsRender,
                                                                            CellBorderThicknessProperty_ChangedCallBack));

    public static void SetCellBorderThickness(UIElement obj, Thickness value)
    {
        obj.SetValue(Son.CellBorderThicknessProperty, value);
    }

    public static Thickness GetCellBorderThickness(UIElement obj)
    {
        return (Thickness)obj.GetValue(Son.CellBorderThicknessProperty);
    }

    private static void CellBorderThicknessProperty_ChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Father father = d as Father;

        if (father != null)
        {
            father.SetBorderThickness((Thickness)e.NewValue);
        }
    }
}    

Father

public class Father : Control
{
    private Son childControl; 

    public void SetBorderThickness(Thickness value)
    {
        if (childControl != null)
        {
            Son.SetCellBorderThickness(childControl, value);
        }
    }
}

Output

enter image description here

Project is available at this link.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top