Reputation: 815
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
Reputation: 5554
Since you are after an explanation, the binding mechanism works likes this:
The binding mechanism depends on two things: the Path and the Source on the Binding
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
If there isn't a DataContext set on the label, it searches for a DataContext up the visual tree.
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
m_Downloader isn't public, and
m_Downloader isn't even a property
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