Bruce Mihura
Bruce Mihura

Reputation: 23

How do I access a Label object, given the TextBox bound its Content property?

My objective is to include the Label's text in an error message if the content of the Label's TextBox is not valid. During validation, when only the TextBox object is easily obtained, I would like to obtain the reference to the Label object which has had its Target property bound to that TextBox.

In other words, given the source of a binding, I would like to return or retrieve the target of that binding. The WPF BindingOperations.GetBindingExpression() and related methods require that the target object be known already.

In WPF XAML I have this:

<Label Target="{Binding ElementName=RatingTextBox}">_Rating:</Label>
<TextBox Name ="RatingTextBox"/>

In C# code-behind I tried this:

BindingExpression be = RatingTextBox.GetBindingExpression(TextBox.TextProperty);
string format = be.ParentBinding.StringFormat;

However, be.ParentBinding above is null even though my TextBox is definitely bound by the label because the hot key "[Alt]-R" works. Can my TextBox get that Label's text somehow from the C# code-behind?

Upvotes: 2

Views: 1378

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70671

If I understand correctly, you are looking for a way to automatically bind the Tooltip property of your TextBox to the Content property of whatever Label object the TextBox is a target of.

Unfortunately, to do this most easily would require a mechanism in WPF to, given the source of a binding, identify its target (or targets…a single source can be bound to multiple targets, of course). And as far as I know, no such mechanism exists as such.

However, I can think of at least a couple of different alternatives that should accomplish a similar effect:

  1. When initializing the window, enumerate all the Label objects to find their targets, and update the targets' Tooltip properties accordingly. Either just set them explicitly, or bind the properties to the Label.Content property.
  2. Reverse the direction the Label target is declared. I.e. create an attached property that can be used on the TextBox object, indicating which Label should target it. Then use this attached property to initialize the Tooltip property appropriate (e.g. in the attached property code, bind or set the Tooltip property, or have some other property that is also bound to the attached property and when it changes, handle the binding or setting there).

The motivation for using an attached property in the second option is to allow the label/target relationship to still be declared just once in the XAML (i.e. avoiding redundancy). It's just that the declaration occurs in the target object (i.e. the TextBox) instead of the label object.

Here are a couple of examples showing what I mean…

First option above:

XAML:

<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <Label x:Name="label1" Content="_Label:" Target="{Binding ElementName=textBox1}"/>
      <TextBox x:Name="textBox1"/>
    </StackPanel>
  </StackPanel>
</Window>

C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        InitTooltips(this);
    }

    private void InitTooltips(FrameworkElement element)
    {
        foreach (FrameworkElement child in
            LogicalTreeHelper.GetChildren(element).OfType<FrameworkElement>())
        {
            Label label = child as Label;

            if (label != null)
            {
                BindingExpression bindingExpression =
                    BindingOperations.GetBindingExpression(label, Label.TargetProperty);

                if (bindingExpression != null)
                {
                    TextBox textBox =
                        FindName(bindingExpression.ParentBinding.ElementName) as TextBox;

                    if (textBox != null)
                    {
                        // You could just set the value, as here:
                        //textBox.ToolTip = label.Content;

                        // Or actually bind the value, as here:
                        Binding binding = new Binding();

                        binding.Source = label;
                        binding.Path = new PropertyPath("Content");
                        binding.Mode = BindingMode.OneWay;

                        BindingOperations.SetBinding(
                            textBox, TextBox.ToolTipProperty, binding);
                    }
                }
            }

            InitTooltips(child);
        }
    }
}


Second option above:

XAML:

<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <!-- Note that the Target property is _not_ bound in the Label element -->
      <Label x:Name="label1" Content="_Label:"/>
      <!-- Instead, it's specified here via the attached property: -->
      <TextBox x:Name="textBox1" l:TooltipHelper.TargetOf="{Binding ElementName=label1}"/>
    </StackPanel>
  </StackPanel>
</Window>

C#:

static class TooltipHelper
{
    public static readonly DependencyProperty TargetOfProperty =
        DependencyProperty.RegisterAttached("TargetOf", typeof(Label),
        typeof(TooltipHelper), new PropertyMetadata(null, _OnTargetOfChanged));

    public static void SetTargetOf(FrameworkElement target, Label labelElement)
    {
        target.SetValue(TargetOfProperty, labelElement);
    }

    public static Label GetTargetof(FrameworkElement target)
    {
        return (Label)target.GetValue(TargetOfProperty);
    }

    private static void _OnTargetOfChanged(
        DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Label oldLabel = (Label)e.OldValue,
            newLabel = (Label)e.NewValue;

        if (oldLabel != null)
        {
            BindingOperations.ClearBinding(oldLabel, Label.TargetProperty);
            BindingOperations.ClearBinding(target, FrameworkElement.ToolTipProperty);
        }

        if (newLabel != null)
        {
            Binding binding = new Binding();

            binding.Source = newLabel;
            binding.Path = new PropertyPath("Content");
            binding.Mode = BindingMode.OneWay;

            BindingOperations.SetBinding(
                target, FrameworkElement.ToolTipProperty, binding);

            binding = new Binding();
            binding.Source = target;
            binding.Mode = BindingMode.OneWay;

            BindingOperations.SetBinding(
                newLabel, Label.TargetProperty, binding);
        }
    }
}

Note that in the second option, no new code is required in the window class. Its constructor can just call InitializeComponent() as usual and that's it. All of the code-behind winds up in the TooltipHelper class, which is referenced in the XAML itself.

Upvotes: 1

Related Questions