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