Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#/WPF: Get Binding Path of an Element in a DataTemplate

How can I get the Binding Path of an Element in a DataTemplate? My XAML looks like this:

<GridViewColumn Header="Double">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TotalValues, Mode=OneWay, StringFormat=\{0:0\'0.00\}, Converter={StaticResource GridValueConverter}}" TextAlignment="Right" Width="auto"/>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Comments" DisplayMemberBinding="{Binding Path=Comments, Mode=OneWay}" Width="auto"/>

To get the Binding Path for the "normal" GridViewColumnHeader.DisplayMemberBinding is

var field = (string)((Binding)((GridViewColumnHeader)e.OriginalSource).Column.DisplayMemberBinding).Path.Path;

How can I get the same for the Binding Path of TextBlock.Text?

like image 435
Joseph jun. Melettukunnel Avatar asked Aug 19 '09 07:08

Joseph jun. Melettukunnel


1 Answers

This is a great question. There's a separation between the code and the XAML, and, code-wise, it's not immediately obvious where to start looking. Also, the DataTemplate is compiled into BAML so it's not very accessible at runtime.

There are at least two strategies for finding the binding's path.

The first strategy is saving the path as a static variable somewhere.

Code-behind:

namespace TempProj
{
    using System.Windows;

    public partial class MainWindow : Window
    {
        static public readonly PropertyPath BindingPath = new PropertyPath("X");

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

XAML:

<Window x:Class="TempProj.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TempProj"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Vector3DCollection x:Key="Coordinates">
            <Vector3D X="1" Y="0" Z="0"/>
            <Vector3D X="0" Y="22" Z="0"/>
            <Vector3D X="0" Y="0" Z="333"/>
            <Vector3D X="0" Y="4444" Z="0"/>
            <Vector3D X="55555" Y="0" Z="0"/>
        </Vector3DCollection>
    </Window.Resources>
    <ListView x:Name="lv" ItemsSource="{StaticResource Coordinates}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="{x:Static local:MainWindow.BindingPath}">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path={x:Static local:MainWindow.BindingPath}}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

The second strategy is opening Snoop or WPF Inspector. The goal is to programmatically search the visual tree for the TextBlock of interest. However, there could many TextBlocks in the ListView. In fact, the Header is probably using one. So, the first step is to identify a unique ancestor of the cell's TextBlock. Looking at the visual tree, there are two decent candidates: a ScrollContentPresenter (with a template part name, which should be unique) and a GridViewRowPresenter. It's best for the ancestor to be close to the TextBlock of interest. This decreases the likelihood of other TextBlocks distorting the search results. Thus, the GridViewRowPresenter is preferable.

enter image description here

One or two utility methods are added to perform the visual tree search.

static public class ControlAux
{
    static public IEnumerable<T> GetVisualDescendants<T>(this DependencyObject item) where T : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(item); ++i)
        {
            DependencyObject child = VisualTreeHelper.GetChild(item, i);
            if (typeof(T) == (child.GetType()))
            {
                yield return (T)child;
            }
            foreach (T descendant in GetVisualDescendants<T>(child))
            {
                yield return descendant;
            }
        }
    }
    static public T FindVisualDescendant<T>(this DependencyObject item, string descendantName) where T : DependencyObject
    {
        return
            GetVisualDescendants<T>(item).Where(
            descendant =>
            {
                var frameworkElement = descendant as FrameworkElement;
                return frameworkElement != null ? frameworkElement.Name == descendantName : false;
            }).
            FirstOrDefault();
    }
}

Now, two searches through the visual tree are performed, with the first search result acting as the root for the second search. Starting with the ListView, a GridViewRowPresenter is found. Starting with that GridViewRowPresenter, a TextBlock is found. Its Text binding is queried and the path is finally accessed.

GridViewRowPresenter gvrp = lv.GetVisualDescendants<GridViewRowPresenter>().FirstOrDefault();
TextBlock tb = gvrp.GetVisualDescendants<TextBlock>().FirstOrDefault();
string path = BindingOperations.GetBinding(tb, TextBlock.TextProperty).Path.Path;

It's important to note that the ListView's ControlTemplates and DataTemplates must be inflated into their actual visual elements for the search to work. If the inflation hasn't happened, the elements don't exist. You can test this by first trying the search in the main window's contructor and then trying it in the window's OnSourceInitialized. Also, all error checking has been left out for brevity.

Finally, this second strategy is not even remotely bulletproof. WPF elements can have arbitrarily complex visual compositions when new ControlTemplates and DataTemplates are used. However, it's a good starting point for thinking about how you might solve the problem in whatever situation you're in.

like image 56
Alexander Van Berg Avatar answered Sep 30 '22 12:09

Alexander Van Berg