.netwpfdatatemplatefindname

Find TextBox in a DataTemplate


Have some elements in a DataTemplate that I need to access in a code behind event handler.
Below works if the button and the textbox have the same Parent.
How would I handle a more complex layout?
If there a general way for accessing an element in a DataTemplate?

XAML

<DataTemplate x:Key="fieldDateTemplate">
    <StackPanel>
        <DatePicker SelectedDate="{Binding Path=FieldValue}" />
        <TextBox x:Name="tbFindMe" Text="findME"/>
        <Button Content="FindTB" Click="FindTB_click" Width="60" HorizontalAlignment="Left"/>
    </StackPanel>
</DataTemplate>

C#

private void FindTB_click(object sender, RoutedEventArgs e)
{
    Button btn = (Button)sender;
    TextBox tb = ((StackPanel)btn.Parent).FindName("tbFindMe") as TextBox;
}

Had to make an update to the excellent answer provided by dkozl
It was failing if there was a ListBox or ListView in the template as it would stop there
This is the fix that is working for now

private DataTemplate FieldTemplateDetail2(object sender, out ContentPresenter cp)
{
    cp = null;
    if (sender == null) return null;
    var d = sender as DependencyObject;
    DependencyObject dNext = null;
    DataTemplate template = null;
    while (d != null)
    {
        if (d is ContentPresenter)
        {
            Debug.WriteLine("FieldTemplateDetail2 d is ContentPresenter" + d.ToString());
            cp = d as ContentPresenter;
        }

        dNext = VisualTreeHelper.GetParent(d);
        if (dNext != null &&  dNext is ListBoxItem)
        {
            Debug.WriteLine("FieldTemplateDetail2 dNext is ListBoxItem " + d.ToString());
            if (cp != null)
            {
                Debug.WriteLine("FieldTemplateDetail2 cp != null" + cp.ToString());
                cp = cp.ContentTemplate.FindName("fieldTemplateDetail", cp) as ContentPresenter;
                if (cp != null)
                {
                    Debug.WriteLine("FieldTemplateDetail2 cp fieldTemplateDetail != null" + cp.ToString());
                    template = cp.ContentTemplate;
                    if (template == null && cp.ContentTemplateSelector != null)
                        template = cp.ContentTemplateSelector.SelectTemplate(cp.Content, cp);
                    break;
                }
                cp = null;
            }
        }
        //d = VisualTreeHelper.GetParent(d);
        d = dNext;
    }
    return template;
}

Based on All code below this appears to be a fix based on answer from dkozl
With the tb burried in a Grid in an Expandar it was more difficult than the simple example above

var d = sender as DependencyObject;
ContentPresenter cp = null;
while (d != null && !(d is ListBoxItem))
{
    if (d is ContentPresenter) cp = d as ContentPresenter;
    d = VisualTreeHelper.GetParent(d);
}
if (cp != null) cp = cp.ContentTemplate.FindName("fieldTemplateDetail", cp) as ContentPresenter;
if (cp != null)
{               
    var template = cp.ContentTemplate;
    if (template == null && cp.ContentTemplateSelector != null)
        template = cp.ContentTemplateSelector.SelectTemplate(cp.Content, cp);
    if (template != null)
    {
        var tb = template.FindName("tbFindMe", cp) as TextBox;
        if (tb == null) MessageBox.Show("null", "ContentTemplateSelector");
        else  MessageBox.Show(tb.Text, "ContentTemplateSelector");
    }
}

All code as requested

<Window x:Class="ListViewTemplateSelectorWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ListViewTemplateSelectorWPF"
        DataContext="{Binding RelativeSource={RelativeSource self}}"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="bvc" />
        <local:FieldTemplateSelector x:Key="fieldTemplateSelector"/>
        <DataTemplate x:Key="windowTemplate">
            <TextBox x:Name="windowTemplateTB" Text="windowTemplate" />
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0" x:Name="lbFields"
                 ItemsSource="{Binding Path=Fields}"
                 HorizontalContentAlignment="Stretch">
            <ListBox.Resources>
                <DataTemplate x:Key="fieldStringTemplate">
                    <StackPanel x:Name="fieldString" Visibility="Visible">
                        <TextBox Text="{Binding Path=FieldValue}" />
                    </StackPanel>
                </DataTemplate>
                <DataTemplate x:Key="fieldDateTemplate">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <DatePicker Grid.Row="0" SelectedDate="{Binding Path=FieldValue}" />
                        <!--<TextBox Grid.Row="1" x:Name="tbFindMe" Text="findME"/>
                        <Button Grid.Row="2" Content="FindTB" Click="FindTB_click" Width="60" HorizontalAlignment="Left"/>-->
                        <Expander Grid.Row="1" Header="Find">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="auto"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <TextBox Grid.Row="0" x:Name="tbFindMe" Text="findME"/>
                                <Button Grid.Row="1" Content="FindTB" Click="FindTB_click" Width="60" HorizontalAlignment="Left"/>
                            </Grid>
                        </Expander>
                    </Grid>
                </DataTemplate>
            </ListBox.Resources>
            <ListBox.ItemTemplate>
                <DataTemplate DataType="local:Field">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="60"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name}" />
                        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=DisplayValue}" />                   
                        <ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
                                    Visibility="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},
                                                         Path=IsSelected, Converter={StaticResource bvc}}"
                                    x:Name="fieldTemplateDetail"
                                    Content="{Binding}"
                                    ContentTemplateSelector="{StaticResource fieldTemplateSelector}"/>                   
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Grid.Row="1" x:Name="findButton" Content="Waste Button" Width="100" HorizontalAlignment="Left" Click="click_Unselect"/>
    </Grid>
</Window>

using System.ComponentModel;
namespace ListViewTemplateSelectorWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    /// 

    public partial class MainWindow : Window
    {
        private List<Field> fields = new List<Field>();
        public MainWindow()
        {
            fields.Add(new FieldString("String1"));
            fields.Add(new FieldString("String2"));
            fields.Add(new FieldDate("Date1"));
            fields.Add(new FieldDate("Date2"));

            InitializeComponent();          
        }
        public Field CurField { get; set; }
        public List<Field> Fields { get { return fields; } }

        private void click_Unselect(object sender, RoutedEventArgs e)
        {            
            try
            {
                Button tb = this.FindName("findButton") as Button;
                if (tb == null) MessageBox.Show("null");
                else MessageBox.Show(tb.Name);
            }
            catch (Exception Ex)
            {
                MessageBox.Show(Ex.Message, "exception findButton");
            }
            try
            {
                DataTemplate dt = this.FindResource("fieldDateTemplate") as DataTemplate;
                if (dt == null) MessageBox.Show("dt not found");
                else MessageBox.Show("dt found");
            }
            catch (Exception Ex)
            {
                MessageBox.Show(Ex.Message, "exception dt");
            }

            lbFields.SelectedIndex = -1;
        }

        private void FindTB_click(object sender, RoutedEventArgs e)
        {
            var d = sender as DependencyObject;
            while (d != null && !(d is ContentPresenter)) d = VisualTreeHelper.GetParent(d);
            var cp = d as ContentPresenter;
            if (cp != null)
            {
                var template = cp.ContentTemplate;
                if (template == null && cp.ContentTemplateSelector != null)
                    template = cp.ContentTemplateSelector.SelectTemplate(cp.Content, cp);
                if (template != null)
                {
                    var tb = template.FindName("tbFindMe", cp) as TextBox;
                    MessageBox.Show(tb.Text, "ContentTemplateSelector");
                }
            }

            Button btn = (Button)sender;
            //MessageBox.Show("button name = " + btn.Name);
            try
            {
                TextBox tb = ((Grid)btn.Parent).FindName("tbFindMe") as TextBox;
                if (tb == null) MessageBox.Show("null","manual");
                else MessageBox.Show(tb.Text, "manual");
            }
            catch (Exception Ex)
            {
                MessageBox.Show(Ex.Message, "exception manual");
            }
        }
    }
    public abstract class Field : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        private string name;
        public string Name { get { return name; } }
        public abstract string DisplayValue { get; }
        public Field(string Name) { name = Name; }
    }
    public class FieldString : Field
    {
        private string fieldValue;
        public string FieldValue
        {
            get { return fieldValue; }
            set
            {
                if (fieldValue == value) return;
                fieldValue = value;
                NotifyPropertyChanged("FieldValue");
                NotifyPropertyChanged("DisplayValue");
            }
        }
        public override string DisplayValue
        {
            get { return FieldValue; }
        }
        public FieldString(string Name) : base(Name) { }
        public FieldString(string Name, string FieldValue) : base(Name)
        {   fieldValue = FieldValue; } 
    }
    public class FieldDate : Field
    {
        private  DateTime? fieldValue = null;
        public DateTime? FieldValue
        {
            get { return fieldValue; }
            set
            {
                if (fieldValue == value) return;
                fieldValue = value;
                NotifyPropertyChanged("FieldValue");
                NotifyPropertyChanged("DisplayValue");
            }
        }
        public override string DisplayValue
        {
            get { return FieldValue.ToString(); }
        }
        public FieldDate(string Name) 
            : base(Name) { }
        public FieldDate(string Name, DateTime FieldValue)
            : base(Name)
        { fieldValue = FieldValue; }
    }
    public class FieldTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate
            SelectTemplate(object item, DependencyObject container)
        {
            FrameworkElement element = container as FrameworkElement;
            if (item != null && item is Field)
            {
                System.Diagnostics.Debug.WriteLine("Field");
                if (item is FieldString)
                {
                    System.Diagnostics.Debug.WriteLine("FieldString");
                    return element.FindResource("fieldStringTemplate") as DataTemplate;
                }
                if (item is FieldDate)
                {
                    System.Diagnostics.Debug.WriteLine("FieldDate");
                    return element.FindResource("fieldDateTemplate") as DataTemplate;                }

                return element.FindResource("fieldTemplate") as DataTemplate;
            }
            else
                return element.FindResource("fieldTemplate") as DataTemplate;
        }
    }
}

Solution

  • To find controls by name inside DataTemplate you need to find ContentPresenter that uses this template and call FindName on that template with found ContentPresenter:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
       var d = sender as DependencyObject;
       ContentPresenter cp = null;
       while (d != null && !(d is ListBoxItem))
       {
           if (d is ContentPresenter) cp = d as ContentPresenter;
           d = VisualTreeHelper.GetParent(d);
       }
       if (cp != null)
       {
          cp = cp.ContentTemplate.FindName("fieldTemplateDetail", cp) as ContentPresenter;
          if (cp != null)
          {
             var template = cp.ContentTemplate;
             if (template == null && cp.ContentTemplateSelector != null)
                template = cp.ContentTemplateSelector.SelectTemplate(cp.Content, cp);
             if (template != null)
             {
                var tb = template.FindName("tbFindMe", cp) as TextBox;
             }
          }
       }
    }