2

I have a simple UserControl that displays an icon and text:

<UserControl x:Class="IconLabel"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="26" d:DesignWidth="200" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Image x:Name="imgIcon" Source="{Binding Path=IconPath}" Stretch="UniformToFill" Width="26" Height="26" Margin="3,0" />
        <Label Content="{Binding Path=LabelText}" Margin="5,0" Grid.Column="1" />
    </Grid>
</UserControl>

The code-behind defines two DependencyProperties that are meant to be bound from the outside:

Public Class IconLabel

    Public Property IconPath As String
        Get
            Return GetValue(IconPathProperty)
        End Get
        Set(ByVal value As String)
            SetValue(IconPathProperty, value)
        End Set
    End Property

    Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabel), New PropertyMetadata(""))

    Public Property LabelText As String
        Get
            Return GetValue(LabelTextProperty)
        End Get
        Set(ByVal value As String)
            SetValue(LabelTextProperty, value)
        End Set
    End Property

    Public Shared ReadOnly LabelTextProperty As DependencyProperty = DependencyProperty.Register("LabelText", GetType(String), GetType(IconLabel), New PropertyMetadata("LabelText"))
End Class

That's working fine so far. I can set its properties in XAML and they are getting used properly:

<local:IconLabel LabelText="Test"/>

However, I'd now like to re-use this control in another UserControl that slightly expands its functionality by showing a progress bar next to it (I've kept this short for the sake of the example):

<UserControl x:Class="IconLabelProgress"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:myApp"
         mc:Ignorable="d" 
         d:DesignHeight="26" d:DesignWidth="600" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4*" MaxWidth="300"/>
            <ColumnDefinition Width="6*"/>
        </Grid.ColumnDefinitions>
        <local:IconLabel IconPath="{Binding Path=IconPath}" LabelText="{Binding Path=PropName}" />
        <ProgressBar Value="{Binding Path=ActualValue}" Minimum="0" Maximum="10" Margin="5" Height="16" VerticalAlignment="Top" Grid.Column="1" />
    </Grid>
</UserControl>

with the following code-behind:

Public Class IconLabelProgress

    'These are just meant to be passed along to the IconLabel
    Public Property IconPath As String
        Get
            Return GetValue(IconPathProperty)
        End Get
        Set(ByVal value As String)
            SetValue(IconPathProperty, value)
        End Set
    End Property

    Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabelProgress), New PropertyMetadata(""))

    Public Property PropName As String
        Get
            Return GetValue(PropNameProperty)
        End Get
        Set(ByVal value As String)
            SetValue(PropNameProperty, value)
        End Set
    End Property

    Public Shared ReadOnly PropNameProperty As DependencyProperty = DependencyProperty.Register("PropName", GetType(String), GetType(IconLabelProgress), New PropertyMetadata("PropName"))

    'This one is new
    Public Property ActualValue As Double
        Get
            Return GetValue(ActualValueProperty)
        End Get
        Set(ByVal value As Double)
            SetValue(ActualValueProperty, value)
        End Set
    End Property

    Public Shared ReadOnly ActualValueProperty As DependencyProperty = DependencyProperty.Register("ActualValue", GetType(Double), GetType(IconLabelProgress), New PropertyMetadata(0.0))
End Class

If I now try to instantiate this control and pass in a value for the label of the inner IconLabel control, like this:

<local:IconLabelProgress x:Name="ilp1" PropName="Test" ActualValue="5.0" />

then it won't show "Test" on its label and instead fall back to its default that was specified via PropertyMetadata("LabelText"). The ActualValue is used correctly, though.

How can I make the outer control pass the value to the nested one?

Crusha K. Rool
  • 1,502
  • 15
  • 24

2 Answers2

3

As a general rule, never explicitly set the DataContext property of a UserControl as you do with

<UserControl x:Class="IconLabel" ...
    DataContext="{Binding RelativeSource={RelativeSource Self}}">

Doing so effectively prevents inheriting a DataContext from the UserControl's parent, e.g. here

<local:IconLabel LabelText="{Binding Path=PropName}" ... />

where PropName is expected to be a property in the parent DataContext.


Instead of explicitly setting a UserControl's DataContext, write its "internal" Bindings with a RelativeSource like

<Label Content="{Binding Path=LabelText,
                 RelativeSource={RelativeSource AncestorType=UserControl}}" ... />
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • This doesn't seem to work for me. By removing the `DataContext` and setting the `RelativeSource` directly, the `IconLabel` won't even show the values when using it directly (without the outer `IconLabelProgress`). The `Label`'s content just stays blank. – Crusha K. Rool Dec 03 '16 at 21:51
  • Sorry, my bad. Replace `RelativeSource Self` with `RelativeSource AncestorType=UserControl`. – Clemens Dec 03 '16 at 21:56
  • Okay, this seems to be on the right track, but not completely there yet. I can now use the `IconLabel` directly again, but I still don't seem to be able (or know how) to pass a value that is set in the `IconLabelProgress` on to the inner `IconLabel`. It just shows the DependencyProperty's default value "LabelText". [Also, I had to add "Mode=FindAncestor" in addition to the AncestorType or it would give me an error.] – Crusha K. Rool Dec 03 '16 at 22:28
  • You should of course also remove the explicit DataContext from the outer UserControl, add write its Bindings with RelativeSource, e.g. `` – Clemens Dec 03 '16 at 22:32
  • Ah, yes. I already had the `DataContext` removed and also added the individual `RelativeSource` entries, but didn't set them to `FindAncestor` yet. Seems to be working now. :) – Crusha K. Rool Dec 03 '16 at 22:38
  • Are you using an older WPF version? I don't need to explicitly specify FindAncestor with .NET 4.6. – Clemens Dec 03 '16 at 22:40
  • Yeah, I am on .NET 4.0 to still support Windows XP :( – Crusha K. Rool Dec 03 '16 at 22:50
0

By default (and you didn't specify anything else) the binding is resolved from the objects DataContext. So your IconLabel searches a property with the name IconPath on its DataContext.

To specify that the place to search for the property is the outer control, you can add ElementName to the binding and set a name property on the IconLabelProgress or you specify a RelativeSource like in the second example of the accepted answer in How do I use WPF bindings with RelativeSource.

Hope it helps.

Community
  • 1
  • 1
Jürgen Röhr
  • 886
  • 6
  • 10