C#/ WPF: получить путь привязки элемента в DataTemplate

как я могу получить путь привязки элемента в DataTemplate? Мой XAML выглядит так:

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

чтобы получить путь привязки для "нормального" GridViewColumnHeader.DisplayMemberBinding составляет

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

как я могу получить то же самое для пути привязки TextBlock.Text?

1 ответов


это большой вопрос. Существует разделение между кодом и XAML, и, с точки зрения кода, не сразу очевидно, с чего начать поиск. Кроме того, DataTemplate компилируется в BAML, поэтому он не очень доступен во время выполнения.

существует по крайней мере две стратегии поиска пути привязки.

на первая стратегия сохраняет путь как статическую переменную где-то.

код:

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>

на вторая стратегия открытия Snoop или инспектор WPF. Цель состоит в программном поиске интересующего текстового блока в визуальном дереве. Однако в ListView может быть много текстовых блоков. На самом деле, заголовок, вероятно, использует один. Итак, первым шагом является идентификация уникального предка клетки элемент TextBlock. Глядя на визуальное дерево, есть два достойных кандидата: ScrollContentPresenter (с именем части шаблона, которое должно быть уникальным) и GridViewRowPresenter. Лучше всего, чтобы предок был близок к интересующему текстовому блоку. Это уменьшает вероятность искажения результатов поиска другими текстовыми блоками. Таким образом, GridViewRowPresenter предпочтительнее.

enter image description here

один или два метода утилиты добавляются для выполнения визуальный поиск дерева.

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();
    }
}

теперь выполняется два поиска по визуальному дереву, причем первый результат поиска действует как корень для второго поиска. Начиная с ListView, gridviewrowpresenter найден. Начиная с этого GridViewRowPresenter, TextBlock найден. Его текстовая привязка запрашивается, и путь, наконец, доступен.

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

важно заметить что ControlTemplates и DataTemplates ListView необходимо надуть в их фактическое визуальные элементы для поиска работы. Если инфляции не произошло, элементов не существует. Вы можете проверить это, сначала попробовав поиск в contructor главного окна, а затем попробовав его в OnSourceInitialized окна. Кроме того, вся проверка ошибок была оставлена для краткости.

наконец, эта вторая стратегия даже отдаленно пуленепробиваемый. Элементы WPF могут иметь произвольно сложные визуальные композиции, Когда новые ControlTemplates и Использовать DataTemplates являются. Однако это хорошая отправная точка для размышлений о том, как вы могли бы решить проблему в любой ситуации, в которой вы находитесь.