Search code examples
wpfc#-4.0attached-properties

WPF Attached Property triggering twice


I am trying to learn dependency properties and attached properties, so please forgive me if you find no use in what I am trying to do.

I have a usual MVVM approach with a Window whose datacontext is set to a VM, and View which is a datatemplate containing a usercontrol targetting such VM.

I am trying to make the window container as dumb as possible, as such i'm trying to define some parameters that usually reside in the window XAML (e.g. the height) via the usercontrol using Attached Properties.

For that purpose I created the following class where I define the attached property:

public static class WpfExtensions
{
    public static readonly DependencyProperty ContainerHeightProperty = DependencyProperty.RegisterAttached(
      "ContainerHeight",
      typeof(double),
      typeof(WpfExtensions),
      new FrameworkPropertyMetadata(300.0, FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender, ContainerHeight)
    );
    public static void SetContainerHeight(UIElement element, double value)
    {
        element.SetValue(ContainerHeightProperty, value);
    }
    public static double GetContainerHeight((UIElement element)
    {
        return (double)element.GetValue(ContainerHeightProperty);
    }

    private static void ContainerHeight(DependencyObject d, DependencyPropertyChangedEventArgs e)  
    {  
       if (d is UserControl)
       {
           UserControl l_Control = (UserControl)d;

           Binding l_Binding = new Binding();

           l_Binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Window), 1);

           l_Binding.Path = new PropertyPath("Height");

           l_Binding.Mode = BindingMode.OneWayToSource;

           BindingOperations.SetBinding(d, e.Property, l_Binding);           
       }
    }  
}

as you can see, to achieve the control of the container window height I am creating a binding in code from the attached property up to the container window.

However the ContainerHeight change get fired twice. The first time I get a change from 300 (the default) to 1024(what is defined in XAML). Then I immediately receive another one from 1024 back to 300 and I am not understanding why.

The window code is very simple:

<Window x:Class="WpfApplication.DialogWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lcl="clr-namespace:WpfApplication"
        Title="DialogWindow">
    <Window.Resources>
        <DataTemplate DataType="{x:Type lcl:Dialog_VM}">
            <lcl:Dialog_VM_View />
        </DataTemplate>
    </Window.Resources>

    <ContentPresenter Content="{Binding }" />

</Window>

and finally the simple ViewModel

<UserControl x:Class="WpfApplication.Dialog_VM_View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:lcl="clr-namespace:WpfApplication"
    mc:Ignorable="d" 
    d:DesignHeight="300" d:DesignWidth="300"
    lcl:WpfExtensions.ContainerHeight="1024">

    <StackPanel>
        <TextBlock Text="This is a test" />
    </StackPanel>

</UserControl>

Solution

  • You should bind the Height property of the parent window to the attached property and not the other way around, shouldn't you?

    This sets (binds) the Height of the parent window to 1024 which is the value of the dependency property in the UserControl:

    private static void ContainerHeight(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UserControl)
        {
            UserControl l_Control = (UserControl)d;
            if (!l_Control.IsLoaded)
            {
                l_Control.Loaded += L_Control_Loaded;
            }
            else
            {
                Bind(l_Control);
            }
        }
    }
    
    private static void L_Control_Loaded(object sender, RoutedEventArgs e)
    {
        UserControl l_Control = (UserControl)sender;
        Bind(l_Control);
    }
    
    private static void Bind(UserControl l_Control)
    {
        Window window = Window.GetWindow(l_Control);
    
        Binding l_Binding = new Binding();
        l_Binding.Path = new PropertyPath(WpfExtensions.ContainerHeightProperty);
        l_Binding.Source = l_Control;
    
        BindingOperations.SetBinding(window, Window.HeightProperty, l_Binding);
    }