How can I bind to a control without a Name or an x:Name?
Asked Answered
S

3

7

Right, so I'm following a tutorial. This one, to be exact. Some code that it provides is this:

<TextBlock Margin="2" Foreground="Red" FontWeight="Bold" 
           Text="{Binding ElementName=AddressBox, 
           Path=(Validation.Errors),
           Converter={StaticResource eToMConverter}}" />

As you can see, it binds to a TextBox's validation errors, and that TextBox's x:Name is AddressBox. Now, my problem is: I've got a Window a bit like this one. It also has only one TextBox. However, I'd rather not use Names or x:Names if possible. Is it possible to bind to another Control's Validation.Errors without being that control being named, and that control being the only one of that type of Control on that same Window? The TextBox is on the same level as the ListBox.

Schaaff answered 6/8, 2013 at 11:50 Comment(2)
I would say use x:Name, because any other solution you may come up with will be more complex and less efficient.Sabo
@Sniffer I'm interested to see if there's an alternative.Britt
A
5

Other way apart from binding with ElementName, is using x:Reference but it also needs the target element to have x:Name defined on it. So, that's out of scope here.

Other approach what I can think of without defining name is to bind something like below (binding to parent and than indexer to get target child).

But this is tightly coupled to your Logical tree structure -

    <StackPanel>
        <TextBlock Text="Test"/>
        <TextBlock Text="{Binding Parent.Children[0].Text,
                           RelativeSource={RelativeSource Mode=Self}}"/>
    </StackPanel>

Also, this can be achieved using IValueConverter. As you mentioned that there is only one element of such type in your parent container, you can pass a parent to converter which will traverse the child using VisualTreeHelper class.

     <StackPanel>
        <TextBlock Text="Test"/>
        <TextBlock Text="{Binding Parent, RelativeSource={RelativeSource Self},
                   Converter={StaticResource MyConverter}}"/>
     </StackPanel>

Here is your converter code -

public class MyConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                            System.Globalization.CultureInfo culture)
    {
        if (value is DependencyObject) 
        {
            var textBlock = FindChild<TextBlock>((DependencyObject)value, null);
            return (textBlock == null)?string.Empty:textBlock.Text;
        }
        else
            return String.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
                                  System.Globalization.CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

Here's the method to traverse using VisualTreeHelper. I have put this method in my Utility class, comes handy in many situations -

    public static T FindChild<T>(DependencyObject parent, string childName)
       where T : DependencyObject
    {
        // Confirm parent is valid.  
        if (parent == null) return null;

        T foundChild = null;

        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            // If the child is not of the request child type child 
            T childType = child as T;
            if (childType == null)
            {
                // recursively drill down the tree 
                foundChild = FindChild<T>(child, childName);

                // If the child is found, break so we do not
                // overwrite the found child.  
                if (foundChild != null) break;
            }
            else if (!string.IsNullOrEmpty(childName))
            {
                var frameworkElement = child as FrameworkElement;
                // If the child's name is set for search 
                if (frameworkElement != null
                    && frameworkElement.Name == childName)
                {
                    // if the child's name is of the request name 
                    foundChild = (T)child;
                    break;
                }
            }
            else
            {
                // child element found. 
                foundChild = (T)child;
                break;
            }
        }

        return foundChild;
    }
Anisotropic answered 20/8, 2013 at 12:47 Comment(4)
Wow, that's cool. Now if you could get the children index from the control this would be perfect. Can it be done?Britt
Yeah we can get the index. Also, i have updated with another approach using VisualTreeHelper.Anisotropic
@It'sNotALie - Does it solves your problem or you still looking for other viable solution?Anisotropic
I just haven't yet had time to test it, as it's non-refundable I want to be sure it works before giving it (I'm pretty sure it does, but better be safe than sorry).Britt
R
0

You can also use RelativeSource to connect to another non-named control if that control is a parent of the control with the binding:

<Button Command="{Binding DataContext.ACommand, RelativeSource={RelativeSource 
    FindAncestor, AncestorType={x:Type Views:AView}}}" Margin="0,2,0,2">
    <Image Source="{Binding AnImage}">
        <Image.Style>
            <Style TargetType="{x:Type Image}">
                <Setter Property="Width" Value="16" />
                <Setter Property="Height" Value="16" />
                <Setter Property="Opacity" Value="1.0" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsEnabled, RelativeSource={
                        RelativeSource FindAncestor, AncestorType={x:Type Button}}, 
                        FallbackValue=False}" Value="False">
                        <Setter Property="Opacity" Value="0.5" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>
</Button>

The first Binding with RelativeSource finds the view model (DataContext) from the view. The second one fades the Image when the Button.IsEnabled property is true. Furthermore, you can put this second part into a style and reuse it whenever you have any Image within a Button.Content property without needing to declare any names.

You can find out more from the RelativeSource MarkupExtension page at MSDN.

Regatta answered 6/8, 2013 at 13:27 Comment(1)
It's not an ancestor, they are both inside the same Grid, unfortunately. Thanks for stating this though, there is probably some terribly weird way of using this with attached properties.Britt
D
0

I believe your only other option would be to define a custom markup extension that does what you are requesting. As an example you could define an extension that finds the first element of a certain tag type from the root element of the namescope. Lookup the extensions section in the link below as a starting point.

http://msdn.microsoft.com/en-us/library/ms747254.aspx

Also for an example you can look near the bottom of the following link.

http://www.codeproject.com/Articles/140618/WPF-Tutorial-TypeConverter-Markup-Extension

I qualify my response with saying that I have never implemented one so there might be specific limitations that get in the way of what you are trying to do.

Dabble answered 6/8, 2013 at 14:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.