Crusha K. Rool
Crusha K. Rool

Reputation: 1502

How to bind to properties of a UserControl inside another UserControl?

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?

Upvotes: 2

Views: 2884

Answers (2)

Clemens
Clemens

Reputation: 128106

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}}" ... />

Upvotes: 3

J&#252;rgen R&#246;hr
J&#252;rgen R&#246;hr

Reputation: 949

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.

Upvotes: 0

Related Questions