问题
I have a ListBox including an ItemTemplate with 2 StackPanels. There is a TextBox in the second StackPanel i want to access. (Change it\'s visibility to true and accept user input) The trigger should be the SelectionChangedEvent. So, if a user clicks on an ListBoxItem, the TextBlock gets invisible and the TextBox gets visible.
XAML CODE:
<ListBox Grid.Row=\"1\" Name=\"ContactListBox\" VerticalAlignment=\"Stretch\" HorizontalAlignment=\"Stretch\" ItemsSource=\"{Binding Contacts}\" Margin=\"0,36,0,0\" SelectionChanged=\"ContactListBox_SelectionChanged\">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation=\"Horizontal\" Margin=\"0,0,0,0\">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header=\"Edit Contact\" Click=\"ContactMenuItem_Click\"/>
<toolkit:MenuItem Header=\"Delete Contact\" Click=\"ContactMenuItem_Click\"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<Grid>
<Rectangle Fill=\"{StaticResource PhoneAccentBrush}\"
Width=\"72\" Height=\"72\">
<Rectangle.OpacityMask>
<ImageBrush ImageSource=\"/Images/defaultContactImage.png\" Stretch=\"UniformToFill\"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
<StackPanel>
<TextBox Text=\"{Binding Name}\" TextWrapping=\"Wrap\" Visibility=\"Collapsed\"/>
<TextBlock Text=\"{Binding Name}\" TextWrapping=\"Wrap\" Style=\"{StaticResource PhoneTextExtraLargeStyle}\" />
<TextBlock Text=\"{Binding Number}\" TextWrapping=\"Wrap\" Margin=\"12,-6,12,0\" Style=\"{StaticResource PhoneTextAccentStyle}\"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I guess there are several ways to solve this, but nothing I tried worked.
My current approach looks like this
private void ContactListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxItem listBoxItem = ContactListBox.SelectedItem as ListBoxItem;
DataTemplate listBoxTemplate = listBoxItem.ContentTemplate;
// How to access the DataTemplate content?
StackPanel outerStackPanel = listBoxTemplate.XXX as StackPanel;
StackPanel innerStackPanel = outerStackPanel.Children[1] as StackPanel;
TextBox nameBox = innerStackPanel.Children[0] as TextBox;
TextBlock nameBlock = innerStackPanel.Children[1] as TextBlock;
nameBox.Visibility = System.Windows.Visibility.Visible;
nameBlock.Visibility = System.Windows.Visibility.Collapsed;
}
回答1:
Thank you for your help guys!! Finally i got it. Solved the problem with the VisualTreeHelper. What a great function ^^
private void ContactListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ContactListBox.SelectedIndex == -1)
return;
currentSelectedListBoxItem = this.ContactListBox.ItemContainerGenerator.ContainerFromIndex(ContactListBox.SelectedIndex) as ListBoxItem;
if (currentSelectedListBoxItem == null)
return;
// Iterate whole listbox tree and search for this items
TextBox nameBox = helperClass.FindDescendant<TextBox>(currentSelectedListBoxItem);
TextBlock nameBlock = helperClass.FindDescendant<TextBlock>(currentSelectedListBoxItem);
helperFunction
public T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
// Check if this object is the specified type
if (obj is T)
return obj as T;
// Check for children
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
if (childrenCount < 1)
return null;
// First check all the children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
return child as T;
}
// Then check the childrens children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = FindDescendant<T>(VisualTreeHelper.GetChild(obj, i));
if (child != null && child is T)
return child as T;
}
return null;
}
回答2:
With this edited function you can also search for control by name (its converted from VB.NET):
public T FindDescendantByName<T>(DependencyObject obj, string objname) where T : DependencyObject
{
string controlneve = "";
Type tyype = obj.GetType();
if (tyype.GetProperty("Name") != null) {
PropertyInfo prop = tyype.GetProperty("Name");
controlneve = prop.GetValue((object)obj, null);
} else {
return null;
}
if (obj is T && objname.ToString().ToLower() == controlneve.ToString().ToLower()) {
return obj as T;
}
// Check for children
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
if (childrenCount < 1)
return null;
// First check all the children
for (int i = 0; i <= childrenCount - 1; i++) {
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T && objname.ToString().ToLower() == controlneve.ToString().ToLower()) {
return child as T;
}
}
// Then check the childrens children
for (int i = 0; i <= childrenCount - 1; i++) {
string checkobjname = objname;
DependencyObject child = FindDescendantByName<T>(VisualTreeHelper.GetChild(obj, i), objname);
if (child != null && child is T && objname.ToString().ToLower() == checkobjname.ToString().ToLower()) {
return child as T;
}
}
return null;
}
回答3:
I can't give you a complete answer...
But I think you can use the VisualTreeHelper to iterate through the children of any control http://blogs.msdn.com/b/kmahone/archive/2009/03/29/visualtreehelper.aspx
However, for the effect you are looking for, then I think using the SelectedItem Style might be a better solution - e.g. see this article - http://joshsmithonwpf.wordpress.com/2007/07/30/customizing-the-selected-item-in-a-listbox/
回答4:
Since DataTemplate is a generic template that could be used many times in the code, there is no way to access it by name (x:Name="numberTextBox").
I solved similar problem to this by making a collection of Controls - while Listbox was populating I add Textbox control to the collection.
string text = myCollectionOfTextBoxes[listbox.SelectedIndex].Text;
Till I found a better soultion - Tag property. In your ListboxItem you bind Tag property to the name
Tag="{Binding Name}"
and the to access it
ListBoxItem listBoxItem = ContactListBox.SelectedItem as ListBoxItem;
string name = listBoxItem.Tag.ToString();
回答5:
Use ItemContainerGenerator.
private void ContactListBox_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 1)
{
var container = (FrameworkElement)ContactListBox.ItemContainerGenerator.
ContainerFromItem(e.AddedItems[0]);
StackPanel sp = container.FindVisualChild<StackPanel>();
TextBox tbName = (TextBox) sp.FindName("tbName");
TextBlock lblName = (TextBlock)sp.FindName("lblName");
TextBlock lblNumber = (TextBlock)sp.FindName("lblNumber");
}
}
来源:https://stackoverflow.com/questions/5181063/how-to-access-a-specific-item-in-a-listbox-with-datatemplate