Как я могу получить доступ к ListViewItems в WPF ListView?
В рамках события я хотел бы обратить внимание на конкретный TextBox в шаблоне ListViewItem. XAML выглядит так:
<ListView x:Name="myList" ItemsSource="{Binding SomeList}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Focus this! -->
<TextBox x:Name="myBox"/>
Я пробовал следующее в коде:
(myList.FindName("myBox") as TextBox).Focus();
но я, кажется, неправильно понял документы FindName()
, потому что он возвращает null
.
Также ListView.Items
не помогает, потому что это (конечно) содержит мои связанные бизнес-объекты и без ListViewItems.
Также не существует myList.ItemContainerGenerator.ContainerFromItem(item)
, который также возвращает null.
Ответы
Ответ 1
Чтобы понять, почему ContainerFromItem
не работал у меня, вот какой-то фон. Обработчик событий, где мне нужна эта функция, выглядит так:
var item = new SomeListItem();
SomeList.Add(item);
ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null
После Add()
ItemContainerGenerator
не создает контейнер сразу, потому что событие CollectionChanged
может обрабатываться не-UI-потоком. Вместо этого он запускает асинхронный вызов и ждет, пока поток пользовательского интерфейса обратится к обратному вызову и выполнит формирование элемента управления ListViewItem.
Чтобы получать уведомление, когда это происходит, ItemContainerGenerator
предоставляет событие StatusChanged
, которое запускается после создания всех контейнеров.
Теперь я должен прослушать это событие и решить, хотите ли в данный момент контролировать фокус или нет.
Ответ 2
Как отмечали другие, MyBox TextBox не может быть найден путем вызова FindName в ListView. Однако вы можете получить ListViewItem, который в настоящее время выбран, и использовать класс VisualTreeHelper для получения TextBox из ListViewItem. Для этого выглядит примерно так:
private void myList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myList.SelectedItem != null)
{
object o = myList.SelectedItem;
ListViewItem lvi = (ListViewItem)myList.ItemContainerGenerator.ContainerFromItem(o);
TextBox tb = FindByName("myBox", lvi) as TextBox;
if (tb != null)
tb.Dispatcher.BeginInvoke(new Func<bool>(tb.Focus));
}
}
private FrameworkElement FindByName(string name, FrameworkElement root)
{
Stack<FrameworkElement> tree = new Stack<FrameworkElement>();
tree.Push(root);
while (tree.Count > 0)
{
FrameworkElement current = tree.Pop();
if (current.Name == name)
return current;
int count = VisualTreeHelper.GetChildrenCount(current);
for (int i = 0; i < count; ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(current, i);
if (child is FrameworkElement)
tree.Push((FrameworkElement)child);
}
}
return null;
}
Ответ 3
Я заметил, что название вопроса напрямую не связано с содержанием вопроса, и на него не отвечает и принятый ответ. Я смог "получить доступ к ListViewItems в ListView WPF", используя это:
public static IEnumerable<ListViewItem> GetListViewItemsFromList(ListView lv)
{
return FindChildrenOfType<ListViewItem>(lv);
}
public static IEnumerable<T> FindChildrenOfType<T>(this DependencyObject ob)
where T : class
{
foreach (var child in GetChildren(ob))
{
T castedChild = child as T;
if (castedChild != null)
{
yield return castedChild;
}
else
{
foreach (var internalChild in FindChildrenOfType<T>(child))
{
yield return internalChild;
}
}
}
}
public static IEnumerable<DependencyObject> GetChildren(this DependencyObject ob)
{
int childCount = VisualTreeHelper.GetChildrenCount(ob);
for (int i = 0; i < childCount; i++)
{
yield return VisualTreeHelper.GetChild(ob, i);
}
}
Я не уверен, насколько суетливая рекурсия, но в моем случае это работало нормально. И нет, я раньше не использовал yield return
в рекурсивном контексте.
Ответ 4
Вы можете пересечь ViewTree, чтобы найти набор записей "ListViewItem", который соответствует ячейке, вызванной тестом теста.
Аналогично, вы можете получить заголовки столбцов из родительского представления для сравнения и сопоставления столбца ячейки. Вы можете привязать имя ячейки к имени заголовка столбца в качестве своего ключа для делегирования/фильтрации компаратора.
Например: HitResult находится в TextBlock, который отображается зеленым цветом. Вы хотите получить дескриптор в "ListViewItem".
![введите описание изображения здесь]()
/// <summary>
/// ListView1_MouseMove
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView1_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
if (ListView1.Items.Count <= 0)
return;
// Retrieve the coordinate of the mouse position.
var pt = e.GetPosition((UIElement) sender);
// Callback to return the result of the hit test.
HitTestResultCallback myHitTestResult = result => {
var obj = result.VisualHit;
// Add additional DependancyObject types to ignore triggered by the cell parent object container contexts here.
//-----------
if (obj is Border)
return HitTestResultBehavior.Stop;
//-----------
var parent = VisualTreeHelper.GetParent(obj) as GridViewRowPresenter;
if (parent == null)
return HitTestResultBehavior.Stop;
var headers = parent.Columns.ToDictionary(column => column.Header.ToString());
// Traverse up the VisualTree and find the record set.
DependencyObject d = parent;
do {
d = VisualTreeHelper.GetParent(d);
} while (d != null && !(d is ListViewItem));
// Reached the end of element set as root scope.
if (d == null)
return HitTestResultBehavior.Stop;
var item = d as ListViewItem;
var index = ListView1.ItemContainerGenerator.IndexFromContainer(item);
Debug.WriteLine(index);
lblCursorPosition.Text = $"Over {item.Name} at ({index})";
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
};
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest((Visual)sender, null, myHitTestResult, new PointHitTestParameters(pt));
}
Ответ 5
Мы используем аналогичную технику с новым datagrid WPF:
Private Sub SelectAllText(ByVal cell As DataGridCell)
If cell IsNot Nothing Then
Dim txtBox As TextBox= GetVisualChild(Of TextBox)(cell)
If txtBox IsNot Nothing Then
txtBox.Focus()
txtBox.SelectAll()
End If
End If
End Sub
Public Shared Function GetVisualChild(Of T As {Visual, New})(ByVal parent As Visual) As T
Dim child As T = Nothing
Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
For i As Integer = 0 To numVisuals - 1
Dim v As Visual = TryCast(VisualTreeHelper.GetChild(parent, i), Visual)
If v IsNot Nothing Then
child = TryCast(v, T)
If child Is Nothing Then
child = GetVisualChild(Of T)(v)
Else
Exit For
End If
End If
Next
Return child
End Function
Этот метод должен быть справедливо применим для вас, просто передайте его listviewitem после его создания.
Ответ 6
Или это может быть просто сделано
private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e)
{
//textbox can be catched like this.
var textBox = ((TextBox)sender);
EmailValidation(textBox.Text);
}