Найти TextBox в шаблоне данных
Есть некоторые элементы в DataTemplate, к которым мне нужно получить доступ в коде позади обработчика событий.
Ниже работает, если кнопка и текстовое поле имеют одного и того же родителя.
Как бы я справился с более сложным макетом?
Если есть общий способ доступа к элементу в 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;
}
Пришлось сделать обновление к отличному ответу, предоставленному dkozl
Не удалось, если бы в шаблоне был ListBox или ListView, так как он остановился бы на этом
Это исправление, которое работает на данный момент
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;
}
Основанный на всем коде ниже, это, кажется, исправление, основанное на ответе от dkozl
С ТБ, похороненным в сетке в Expandar, это было сложнее, чем простой пример выше
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");
}
}
Весь код по запросу
<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;
}
}
}
1 ответ
Чтобы найти элементы управления по имени внутри DataTemplate
вам нужно найти ContentPresenter
который использует этот шаблон и вызов FindName
на этом шаблоне с найденным 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;
}
}
}
}
Этот старый пост стал вам спасителем. Я занимался этой проблемой в течение последних нескольких часов, и dkozl предоставил отличную информацию. Надеюсь, мое объяснение поможет некоторым другим.
У меня есть datagrid с некоторыми столбцами (точнее, 7), 5 из них отображают статические данные об элементе, 1 - это текстовое поле / отображение данных, а последний - индикатор включения и выключения для логических элементов.
В настоящее время я улучшаю взаимодействие с пользователем при использовании сетки данных (используя F2 для редактирования, пространство для переключения логических значений и т. Д.)
В приведенном ниже XAML у меня будет только один столбец в datagrid с текстовым полем:
<DataGrid x:Name="my_DG" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" KeyDown="my_DG_KeyDown">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<TextBlock Text="Value" FontSize="18"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtBoxValue" Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" KeyDown="EventTrigger_KeyDown"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsTabStop" Value="False" />
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
<DataGrid.Columns>
</DataGrid>
Эффект, к которому я стремился, - это когда у меня была выделена строка в сетке данных, когда я нажимаю F2, фокус переходит в текстовое поле, и пользователь может начать редактировать значение. Это было сделано с помощью события KeyDown="my_DG_KeyDown". Код мероприятия:
private void my_DG_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.F2))
{
var currRow = (sender as DataGrid).CurrentItem;
//Columns[0] is the column the text box is in for the given row.
var currCell_CP = (sender as DataGrid).Columns[0].GetCellContent(currRow);
var itm = (currCell_CP as ContentPresenter).ContentTemplate.FindName("txtBoxValue", currCell_CP) as TextBox;
itm.Focus();
}
}
Важный вывод: я смог получить ContentPresenter данной ячейки. Затем оттуда я смог получить шаблон и найти имя текстового поля ("txtBoxValue") с этими двумя элементами.
Я нахожу это немного более простым, чем ответ dkozl, но я бы не пришел к этому без его помощи