Как получить доступ к элементу управления внутри шаблона данных XAML?
У меня есть это флипвью:
<FlipView x:Name="models_list" SelectionChanged="selectionChanged">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid x:Name="cv">
<Image x:Name="img1" Source = "{Binding ModelImage}" Stretch="Fill" Tag="{Binding ModelTag}"/>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
Я хочу найти img1 текущего выбранного индекса. При поиске я нашел этот метод в некотором посте здесь:
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name== ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
Он возвращает мне изображение по первому индексу flipview, но мне нужен тот, который присутствует в текущем выбранном индексе. Я попытался отредактировать этот метод, но мне не удалось найти требуемый элемент управления. Может кто-нибудь мне помочь?
6 ответов
Проблема, которую вы испытываете, заключается в том, что DataTemplate
повторяется, и контент генерируется FlipView
, Name
не выставляется, потому что это будет конфликтовать с предыдущим родным братом, который был создан (или со следующим, который будет).
Итак, чтобы получить именованный элемент в DataTemplate
Вы должны сначала получить сгенерированный элемент, а затем искать внутри этого сгенерированного элемента нужный элемент. Помните, что логическое дерево в XAML - это способ доступа к вещам по имени. Сгенерированные элементы не находятся в логическом дереве. Вместо этого они находятся в дереве визуалов (все элементы управления находятся в дереве визуалов). Это означает, что именно в Visual Tree вы должны искать элемент управления, на который хотите сослаться. VisualTreeHelper
позволяет тебе это сделать
Теперь, как это сделать?
Я написал статью об этом, потому что это такой повторяющийся вопрос: http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html но суть решения рекурсивный метод, который выглядит примерно так:
public void TestFirstName()
{
foreach (var item in MyFlipView.Items)
{
var _Container = MyFlipView.ItemContainerGenerator
.ContainerFromItem(item);
var _Children = AllChildren(_Container);
var _FirstName = _Children
// only interested in TextBoxes
.OfType<TextBox>()
// only interested in FirstName
.First(x => x.Name.Equals("FirstName"));
// test & set color
_FirstName.Background =
(string.IsNullOrWhiteSpace(_FirstName.Text))
? new SolidColorBrush(Colors.Red)
: new SolidColorBrush(Colors.White);
}
}
public List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
_List.Add(_Child as Control);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
Ключевая проблема здесь заключается в том, что такой метод получает все дочерние элементы, а затем в результирующем списке дочерних элементов управления вы можете искать нужный элемент управления. Есть смысл?
А теперь ответь на твой вопрос!
Поскольку вы конкретно хотите выбрать текущий элемент, вы можете просто обновить код следующим образом:
if (MyFlipView.SelectedItem == null)
return;
var _Container = MyFlipView.ItemContainerGenerator
.ContainerFromItem(MyFlipView.SelectedItem);
// then the same as above...
Может быть, немного более общий подход может быть что-то вроде этого:
private List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
{
_List.Add(_Child as Control);
}
_List.AddRange(AllChildren(_Child));
}
return _List;
}
private T FindControl<T> (DependencyObject parentContainer, string controlName)
{
var childControls = AllChildren(parentContainer);
var control = childControls.OfType<Control>().Where(x => x.Name.Equals(controlName)).Cast<T>().First();
return control;
}
Вы бы вызвали FindControl следующим образом:
var parentContainer = this.flipView.ItemContainerGenerator.ContainerFromItem(this.flipView.SelectedItem);
var myImage = FindControl<Image>(parentContainer, "img1");
//suppose you want to change the visibility
myImage.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Простое решение для получения доступа к элементам внутри DataTemplate - это обернуть содержимое DataTemplate в UserControl, где вы получите доступ ко всем элементам пользовательского интерфейса в элементе ItemsControl. Я думаю, что FlipView обычно виртуализирует свои элементы, так что даже если у вас есть 100 элементов, связанных с ним - только 2-3 могут иметь текущее представление в пользовательском интерфейсе (1-2 из них скрыты), поэтому вы должны помнить, что когда вы хотите заменить что-либо и вносить изменения только тогда, когда элемент загружен в элемент управления.
Если вам действительно нужно идентифицировать контейнер элемента, который представляет элемент в ItemsSource, вы можете проверить свойство ItemContainerGenerator вашего FlipView и его метод ContainerFromItem().
Чтобы получить координаты предмета, вы можете использоватьGetBoundingRect()
метод расширения в WinRT XAML Toolkit.
В целом, однако, основываясь на вашем комментарии, может оказаться, что лучший подход на самом деле совершенно другой. Если вы привязываете свой FlipView к источнику - обычно вы можете управлять отображением или наложением изображений, изменяя свойства связанных элементов коллекции источника.
Если вам просто нужны координаты экрана при щелчке элемента... Вы можете зарегистрировать обработчик щелчков на изображении, а затем использовать senderimg.transform tovisual(null), что дает вам общий переход, из которого вы можете получить координаты текущей точки.
Я боролся с этим весь день, и кажется, что элемент управления должен быть загружен (обработан), чтобы получить его дочерние элементы управления из DataTemplate. Это означает, что вы не можете использовать этот код или любой другой код (для той же цели) в загруженном, инициализированном окне... Вы можете использовать его после инициализации элемента управления, например, в SelectedItem.
Я предлагаю использовать конвертеры и определить запрашиваемое действие в конвертере вместо прямого контроля доступа.
Поэтому, если вы назначили Source через связывание, почему бы не сделать то же самое с остальными:<FlipView SelectedIndex="{Binding SIndex}" SelectedItem="{Binding SItem}" SelectedValue="{Binding SValue}"/>
Вы должны сначала подготовить место для этого свойства. Это может быть класс, полученный из INotifyPropertyChanged
,
Наймите MVVM, чтобы работать на Вас, больше всего работайте в XAML. Конечно, в начале кажется, что это больше работы, но с более сложным проектом с MVVM будет последовательным и гибким.