Within an event, I'd like to put the focus on a specific TextBox within the ListViewItem's template. The XAML looks like this:
在一个事件中,我想把重点放在ListViewItem模板中的特定TextBox上。 XAML看起来像这样:
<ListView x:Name="myList" ItemsSource="{Binding SomeList}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Focus this! -->
<TextBox x:Name="myBox"/>
I've tried the following in the code behind:
我在后面的代码中尝试了以下内容:
(myList.FindName("myBox") as TextBox).Focus();
but I seem to have misunderstood the FindName()
docs, because it returns null
.
但我似乎误解了FindName()文档,因为它返回null。
Also the ListView.Items
doesn't help, because that (of course) contains my bound business objects and no ListViewItems.
ListView.Items也没有帮助,因为(当然)包含我绑定的业务对象而没有ListViewItems。
Neither does myList.ItemContainerGenerator.ContainerFromItem(item)
, which also returns null.
myList.ItemContainerGenerator.ContainerFromItem(item)也都没有,它也返回null。
6 个解决方案
#1
16
To understand why ContainerFromItem
didn't work for me, here some background. The event handler where I needed this functionality looks like this:
要理解为什么ContainerFromItem不适合我,这里有一些背景知识。我需要此功能的事件处理程序如下所示:
var item = new SomeListItem();
SomeList.Add(item);
ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null
After the Add()
the ItemContainerGenerator
doesn't immediately create the container, because the CollectionChanged
event could be handled on a non-UI-thread. Instead it starts an asynchronous call and waits for the UI thread to callback and execute the actual ListViewItem control generation.
在Add()之后,ItemContainerGenerator不会立即创建容器,因为可以在非UI线程上处理CollectionChanged事件。相反,它启动异步调用并等待UI线程回调并执行实际的ListViewItem控件生成。
To be notified when this happens, the ItemContainerGenerator
exposes a StatusChanged
event which is fired after all Containers are generated.
要在发生这种情况时收到通知,ItemContainerGenerator会公开StatusChanged事件,该事件在生成所有Container之后触发。
Now I have to listen to this event and decide whether the control currently want's to set focus or not.
现在我必须听这个事件并决定控制当前是否想要设置焦点。
#2
14
As others have noted, The myBox TextBox can not be found by calling FindName on the ListView. However, you can get the ListViewItem that is currently selected, and use the VisualTreeHelper class to get the TextBox from the ListViewItem. To do so looks something like this:
正如其他人所说,通过在ListView上调用FindName无法找到myBox TextBox。但是,您可以获取当前选定的ListViewItem,并使用VisualTreeHelper类从ListViewItem获取TextBox。这样做看起来像这样:
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
4
I noticed that the question title does not directly relate to the content of the question, and neither does the accepted answer answer it. I have been able to "access the ListViewItems of a WPF ListView" by using this:
我注意到问题标题与问题的内容没有直接关系,接受的答案也没有回答。我已经能够使用这个“访问WPF ListView的ListViewItems”:
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);
}
}
I'm not sure how hectic the recursion gets, but it seemed to work fine in my case. And no, I have not used yield return
in a recursive context before.
我不确定递归会有多忙,但在我的情况下似乎工作正常。不,我以前没有在递归上下文中使用yield return。
#4
0
You can traverse up the ViewTree to find the item 'ListViewItem' record set that corresponds to the cell triggered from hit test.
您可以遍历ViewTree以查找与命中测试触发的单元格对应的项目“ListViewItem”记录集。
Similarly, you can get the column headers from the parent view to compare and match the cell's column. You may want to bind the cell name to the column header name as your key for your comparator delegate/filter.
同样,您可以从父视图中获取列标题,以比较和匹配单元格的列。您可能希望将单元名称绑定到列标题名称作为比较器委托/过滤器的键。
For example: HitResult is on TextBlock shown in green. You wish to obtain the handle to the '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's 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's 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
-1
We use a similar technique with WPF's new 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
The technique should be fairly applicable for you, just pass your listviewitem once it's generated.
该技术应该适用于您,只需在生成后传递listviewitem即可。
#6
-1
Or it can be simply done by
或者它可以简单地完成
private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e)
{
//textbox can be catched like this.
var textBox = ((TextBox)sender);
EmailValidation(textBox.Text);
}
#1
16
To understand why ContainerFromItem
didn't work for me, here some background. The event handler where I needed this functionality looks like this:
要理解为什么ContainerFromItem不适合我,这里有一些背景知识。我需要此功能的事件处理程序如下所示:
var item = new SomeListItem();
SomeList.Add(item);
ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null
After the Add()
the ItemContainerGenerator
doesn't immediately create the container, because the CollectionChanged
event could be handled on a non-UI-thread. Instead it starts an asynchronous call and waits for the UI thread to callback and execute the actual ListViewItem control generation.
在Add()之后,ItemContainerGenerator不会立即创建容器,因为可以在非UI线程上处理CollectionChanged事件。相反,它启动异步调用并等待UI线程回调并执行实际的ListViewItem控件生成。
To be notified when this happens, the ItemContainerGenerator
exposes a StatusChanged
event which is fired after all Containers are generated.
要在发生这种情况时收到通知,ItemContainerGenerator会公开StatusChanged事件,该事件在生成所有Container之后触发。
Now I have to listen to this event and decide whether the control currently want's to set focus or not.
现在我必须听这个事件并决定控制当前是否想要设置焦点。
#2
14
As others have noted, The myBox TextBox can not be found by calling FindName on the ListView. However, you can get the ListViewItem that is currently selected, and use the VisualTreeHelper class to get the TextBox from the ListViewItem. To do so looks something like this:
正如其他人所说,通过在ListView上调用FindName无法找到myBox TextBox。但是,您可以获取当前选定的ListViewItem,并使用VisualTreeHelper类从ListViewItem获取TextBox。这样做看起来像这样:
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
4
I noticed that the question title does not directly relate to the content of the question, and neither does the accepted answer answer it. I have been able to "access the ListViewItems of a WPF ListView" by using this:
我注意到问题标题与问题的内容没有直接关系,接受的答案也没有回答。我已经能够使用这个“访问WPF ListView的ListViewItems”:
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);
}
}
I'm not sure how hectic the recursion gets, but it seemed to work fine in my case. And no, I have not used yield return
in a recursive context before.
我不确定递归会有多忙,但在我的情况下似乎工作正常。不,我以前没有在递归上下文中使用yield return。
#4
0
You can traverse up the ViewTree to find the item 'ListViewItem' record set that corresponds to the cell triggered from hit test.
您可以遍历ViewTree以查找与命中测试触发的单元格对应的项目“ListViewItem”记录集。
Similarly, you can get the column headers from the parent view to compare and match the cell's column. You may want to bind the cell name to the column header name as your key for your comparator delegate/filter.
同样,您可以从父视图中获取列标题,以比较和匹配单元格的列。您可能希望将单元名称绑定到列标题名称作为比较器委托/过滤器的键。
For example: HitResult is on TextBlock shown in green. You wish to obtain the handle to the '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's 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's 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
-1
We use a similar technique with WPF's new 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
The technique should be fairly applicable for you, just pass your listviewitem once it's generated.
该技术应该适用于您,只需在生成后传递listviewitem即可。
#6
-1
Or it can be simply done by
或者它可以简单地完成
private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e)
{
//textbox can be catched like this.
var textBox = ((TextBox)sender);
EmailValidation(textBox.Text);
}