dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.06k stars 1.17k forks source link

Binding on two different attached properties with the same local name causes both properties to be resolved to one of them #3883

Open dragomirtitian opened 3 years ago

dragomirtitian commented 3 years ago

MainWindow.xaml

<Window x:Class="BugRepro.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:BugRepro">
    <StackPanel>
        <local:UserControl1></local:UserControl1>
        <local:UserControl2></local:UserControl2>
    </StackPanel>
</Window>

UserControl1.xaml

<UserControl x:Class="BugRepro.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:c="clr-namespace:BugRepro.UserControl1Props">
    <StackPanel Orientation="Horizontal" TextBlock.FontSize="50">
        <TextBlock>1</TextBlock>
        <Button x:Name="btn" c:AttachedProps.SomeProp="1">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <StackPanel>
                        <TextBlock Foreground="BlueViolet" Text="{Binding Path=(c:AttachedProps.SomeProp), RelativeSource={RelativeSource Mode=TemplatedParent}}"></TextBlock>
                    </StackPanel>
                </ControlTemplate>
            </Button.Template>
        </Button>
    </StackPanel>
</UserControl>

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace BugRepro
{
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
    }

    namespace UserControl1Props
    {
        public class AttachedProps
        {
            public static int GetSomeProp(DependencyObject obj) => (int)obj.GetValue(SomePropProperty);
            public static void SetSomeProp(DependencyObject obj, int value) => obj.SetValue(SomePropProperty, value);

            // Using a DependencyProperty as the backing store for SomeProp.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty SomePropProperty =
                DependencyProperty.RegisterAttached("SomeProp", typeof(int), typeof(AttachedProps), new PropertyMetadata(-1));
        }
    }
}

UserControl1.xaml

<UserControl x:Class="BugRepro.UserControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:c="clr-namespace:BugRepro.UserControl2Props">
    <StackPanel Orientation="Horizontal" TextBlock.FontSize="50">
        <TextBlock>2</TextBlock>
        <Button x:Name="btn" c:AttachedProps.SomeProp="2">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <StackPanel>
                        <TextBlock Foreground="BlueViolet" Text="{Binding Path=(c:AttachedProps.SomeProp), RelativeSource={RelativeSource Mode=TemplatedParent}}"></TextBlock>
                    </StackPanel>
                </ControlTemplate>
            </Button.Template>
        </Button>
    </StackPanel>
</UserControl>

UserControl2.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace BugRepro
{
    public partial class UserControl2 : UserControl
    {
        public UserControl2()
        {
            InitializeComponent();
        }
    }

    namespace UserControl2Props
    {
        public class AttachedProps
        {
            public static int GetSomeProp(DependencyObject obj) => (int)obj.GetValue(SomePropProperty);

            public static void SetSomeProp(DependencyObject obj, int value) => obj.SetValue(SomePropProperty, value);

            // Using a DependencyProperty as the backing store for SomeProp.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty SomePropProperty =
                DependencyProperty.RegisterAttached("SomeProp", typeof(int), typeof(AttachedProps), new PropertyMetadata(-2));
        }
    }
}

The code above will display two controls, both with one static text, and one through a binding on an attached property. Usercontrol1 should display 1 1, while should display Usercontrol1 should display 2 2. In actuality Usercontrol2 displays 2 -1. This is because teh binding in Usercontrol2 will use the attached property in UserControl1Props which has a default of -1

image

If you change the local alias of the namespace from c to cx in any of the user controls the code works as expected:

image

daisyTian0517 commented 3 years ago

You create your SomeProp with public static readonly DependencyProperty which means you can use it as a global attached properties for each button. You define two attachecd DependencyProperty with the same name, which can cause confusion when used. You can name the DependencyProperty as SomeProp2 for your project, then you don't need to change the local alias of the namespace to cx.

dragomirtitian commented 3 years ago

@daisyTian0517 The real world issue is a bit more complex. I agree under normal circumstances, you can rename the class or the property but in my specific use case this is not practical. The issue occurs for me when a container application loads two plugins, that use two different version of a shared library. The dependency property in conflict comes from the different versions of the same library, so renaming the property each time you create a new version is not really practical. (The workaround we are working on is renaming the local alias as a pre build step when creating the release build, but this isn't exactly great either)

lostmsu commented 2 years ago

I think I might have a very similar issue where binding uses attached property instead of the main property with the same name. E.g. I have a MyControl with its own DependencyProperty Prop of type IEnumerable, and also an attached property with the same name Prop defined in a custom class Behavior.

Given this setup, on loading XAML I randomly get the binding to be attached to either MyControl.Prop or Behavior.Prop:

<Grid>
  <Grid.Resources>
    <ResourceDictionary>
      <CollectionContainer x:Key="MyCollection" Collection="{Binding Prop, Source={x:Reference MyControl1}}"/>
    </ResourceDictionary>
  </Grid.Resources>

  <my:MyControl x:Name="MyControl1">
    <my:Behavior.Prop>
      <sys:Double>40</sys:Double>
    </my:Behavior.Prop>
  </my:MyControl>
</Grid>

From my understanding, MyCollection should never bind to the attached property because I did not specify the full path to it in Binding (e.g. it would have to be {Binding (my:Behavior.Prop), ...} to use attached property). It should always bind to MyControl.Prop.