deha
deha

Reputation: 815

can't bind to own dependency property

I have a Downloader, which has a progress property, which is:

    public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(int), typeof(Downloader), new PropertyMetadata(0, new PropertyChangedCallback(OnProgressChange)));

    public int Progress
    {
        get { return (int)this.GetValue(ProgressProperty); }
        private set { this.SetValue(ProgressProperty, value); }
    }

    private static void OnProgressChange(DependencyObject @object, DependencyPropertyChangedEventArgs e)
    {
        Downloader d = @object as Downloader;
        if (d.PropertyChanged != null) d.PropertyChanged(d, new PropertyChangedEventArgs("Progress"));
    }

When I try to consume it in other class xaml (m_downloader is a private field of Downloader type):

<sdk:Label Height="24" HorizontalAlignment="Left" Margin="360,12,0,0" Name="ProgressLabel" VerticalAlignment="Top" Width="38" Content="{Binding Path=m_downloader.Progress, StringFormat='\{0\} %'}" />

nothing happens. During debug I can see that d.PropertyChanged is always null. How make it work to display progress in label?

EDIT 1:

m_downloader works like this (after temporary change to property):

public partial class AudioPlayer : UserControl
{
    public AudioPlayer()
    {
        m_downloader = new Downloader();
        InitializeComponent();
    }
    ...
    public Downloader m_downloader {get; private set;}
}

EDIT 2:

I've done the binding in Blend, it changed the xaml to this:

<UserControl x:Name="audioPlayer" x:Class="Media.Controls.AudioPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="125" d:DesignWidth="410" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

<Grid x:Name="LayoutRoot" Background="White">
    ...
    <sdk:Label Height="24" HorizontalAlignment="Left" Margin="337,12,0,0" Name="ProgressLabel" VerticalAlignment="Top" Width="61" Content="{Binding m_downloader.Progress, ElementName=audioPlayer, Mode=OneWay}" />
    ...
</Grid>
</UserControl>

Why the ElementName is set to the name of nesting, instead of the nested one? It doesn't make sense to me... But it's working after this change.

Upvotes: 0

Views: 704

Answers (1)

Chui Tey
Chui Tey

Reputation: 5554

Since you are after an explanation, the binding mechanism works likes this:

  1. The binding mechanism depends on two things: the Path and the Source on the Binding

  2. The Path in your case, is the "m_Downloader.Progress", and if the source isn't set explicitly, the binding falls back to using the DataContext on the label

  3. If there isn't a DataContext set on the label, it searches for a DataContext up the visual tree.

  4. You can set the source explicitly using {Binding Source=...} or {Binding ElementName=...}

For example, the following will not work:

<sdk:Label 
   x:Name="ProgressLabel"
   Content="{Binding Path=Progress}" />

This is because there is no source, and DataContext isn't set.

You can set this in code using:

ProgressLabel.DataContext = m_Downloader

Alternately, you might wish to bind relative to the nesting object.

<sdk:Label
   Content="{Binding m_Downloader.Progress}" />

However, this will not work as

  1. m_Downloader isn't public, and

  2. m_Downloader isn't even a property

  3. DataContext isn't set.

To resolve this, use the following:

public Downloader Downloader {get; set;|

public void InitializeComponent() { // ...

 // set DataContext to nesting object
 ProgressLabel.DataContext = this; 

}

If you don't want to set DataContext in code, you can set the source explicitly. Since AudioPlayer has an Element Name

<UserControl x:Name="AudioPlayer" ... />

You can reference it in your binding

<sdk:Label
   Content="{Binding m_Downloader.Progress, ElementName=AudioPlayer}" />

Finally, you can also do this by setting the DataContext on the AudioPlayer to itself.

<UserControl
   DataContext="{Binding RelativeSource={RelativeSource Self}}" />

<sdk:Label
   Content="{Binding m_Downloader.Progress}" />

Upvotes: 1

Related Questions