Search code examples
c#wpfuwpwindows-store-apps

UWP bugs? Binding to some nested DependencyProperty would not working


I'm developing a chart control which is derived from a UserControl, everything is fine until I found that a DependencyProperty of bottom axis can not be set through binding. Here is the code snippet of the chart control:

  public class AxisBase : FrameworkElement
    {
        public IList<double> ExtraGridLines
        {
            get { return (IList<double>)GetValue(ExtraGridLinesProperty); }
            set { SetValue(ExtraGridLinesProperty, value); }
        }
        public static readonly DependencyProperty ExtraGridLinesProperty =
            DependencyProperty.Register("ExtraGridLines", typeof(IList<double>), typeof(AxisBase), new PropertyMetadata(null, OnExtraGridLineDataPropertyChanged));
        private static void OnExtraGridLineDataPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Problem: data bing would not work, and 
            //this call back method will never get called!
            Debug.WriteLine("ExtraGridLines changed...");
        }
    }

    public sealed partial class MyChart : UserControl
    {
        public MyChart()
        {
            this.InitializeComponent();
        }

        public AxisBase BottomAxis
        {
            get { return (AxisBase)GetValue(BottomAxisProperty); }
            set { SetValue(BottomAxisProperty, value); }
        }
        public static readonly DependencyProperty BottomAxisProperty =
            DependencyProperty.Register("BottomAxis", typeof(AxisBase), typeof(MyChart), new PropertyMetadata(null));


    }

and here is the binding code:

<Page x:Class="App1.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App1"
      x:Name="rootPage">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <local:MyChart>
            <local:MyChart.BottomAxis>
                <local:AxisBase ExtraGridLines="{Binding MyExtraGridLines, ElementName=rootPage}" />
            </local:MyChart.BottomAxis>
        </local:MyChart>
    </Grid>
</Page>


public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    private List<double> _myExtraGridLines;
    public List<double> MyExtraGridLines
    {
        get { return _myExtraGridLines; }
        set
        {
            _myExtraGridLines = value;
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyExtraGridLines"));
        }
    }

    public MainPage()
    {
        this.InitializeComponent();
        this.MyExtraGridLines = new List<double>() { 10, 100, 1000 };
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

The problem is: the binding seems never get worked, the PropertyChangedCallback method of dp ExtraGridLines is never called. However these code works in WPF. What's wrong with my code? or is it a bug? Any idea would be appreciate!

Edit1: This seems having nothing to do with data type, even I've changed the type from IList<double> to int, string or object, the problem is same. But if I change the type of AxisBase from FrameworkElement to DependencyObject, then the binding would work. But this solution is not acceptable, since DependencyObject has no Style.

Edit2: Finally, I think I've found the reason why ElementName binding would not work: ElementName binding only works for elements in the same NameScope. If you're interested, for more information, please see these two good links: https://stackoverflow.com/questions/18389118/how-does-binding-elementname-work-exactly and http://www.cnblogs.com/idior/archive/2010/05/28/1746513.html

btw 1: I was wrong at the very first: these code would not work in WPF either. The binding would throw a runtime error: System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=rootPage'. BindingExpression:Path=ShowAxisLabel; DataItem=null; target element is 'AxisBase' (Name=''); target property is 'IsLabelVisible' (type 'Boolean')

btw 2: To make DataContext binding work in the above code, AxisBase should listen to the change of the DataContext of MyChart, or be added to the Logical or Visual tree of MyChart explicitly. That's a bug of my code. But due to NameScope problem, there seems no normal or elegant way to make ElementName binding here.

Probably I should change the title of this question to: Why ElementName binding to element in UserControl would not work in UWP/WPF?


Solution

  • This is likely a bug with traditional binding in UWP; however, with the new x:Bind, the following code should be working as expected.

    <local:AxisBase ExtraGridLines="{x:Bind MyExtraGridLines, Mode=OneWay}" />

    Note this gives you better performance too. So you should always consider using this binding approach first.

    Update

    Looks like the real issue is related to ElementName binding to an ancestor. But if you use the normal MVVM approach by specifying the DataContext like this -

    MyExtraGridLines = new List<double>() { 10, 100, 1000 };
    
    DataContext = this;
    

    And removing the ElementName binding with a normal one -

    <local:AxisBase ExtraGridLines="{Binding MyExtraGridLines}" />
    

    Also make sure the axis element is in the visual tree by adding -

    <UserControl x:Class="xxx.MyChart">
        <Grid>
            <ContentPresenter Content="{x:Bind BottomAxis, Mode=OneWay}" />
        </Grid>
    </UserControl>
    

    This should work too.