I believe it is because the change events for those properties on the MediaElement are of type RoutedEventHandler instead of type RoutedPropertyChangedEventHandler (like the ValueChanged event on something like a Slider would be). The initial element binding is working fine since DownloadProgress is indeed a DependencyProperty, it is just never getting any type of notification that the property changes so it will never update.
This means you are pretty much stuck keying off of the DownloadProgressChanged event in some way. If you really wanted to avoid using code-behind I would suggest some kind of a Blend TargettedTriggerAction. There would still be a tiny bit of code, but it would be marked up declaratively in XAML and at least it would be reusable and out of your code-behind.
Something like this might do the trick (I am handling a conversion into % and decimal formatting here as well):
[TypeConstraint(typeof(MediaElement))]
public class UpdateDownloadProgressBehavior : TargetedTriggerAction<TextBlock>
{
protected override void Invoke(object parameter)
{
MediaElement mediaElement = (MediaElement)this.AssociatedObject;
this.Target.Text = (mediaElement.DownloadProgress * 100).ToString("0.0");
}
}
You would then invoke this as an action for a DownloadProgressChanged EventTrigger in your XAML and give the TextBlock you want to update as the target.
<MediaElement x:Name="MyMediaElement">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DownloadProgressChanged">
<local:UpdateDownloadProgressBehavior TargetName="MyTextBlock"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MediaElement>
I'm not sure if this seems any cleaner to you than wiring it up directly in code-behind, but I personally like it a little bit better. The only other solution I can think of is some kind of BindingConduit helper as described here which could implement INotifyPropertyChanged and handle the notifications, but I am not entirely sure what is involved with using that with a MediaElement.