如何使用DataTemplate在Listbox中访问特定项?

14

我有一个ListBox,其中包含一个具有2个StackPanels的ItemTemplate。 第二个StackPanel中有一个TextBox,我想要访问它。 (将其可见性更改为true并接受用户输入) 触发器应该是SelectionChangedEvent。 因此,如果用户单击ListBoxItem,则TextBlock变为不可见,并且TextBox变为可见。

XAML代码:

<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>

我猜有几种方法可以解决这个问题,但是我尝试的一切都没有奏效。

我的当前方法看起来像这样

    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
我喜欢这个解决方案,但是如果我有几个文本块,并且我想使第二个或第三个可见/折叠,该怎么办?换句话说,如何进入具有指定名称的列表框控件? - dargod
@sust86,listBoxTemplate.XXX中的XXX是什么? - ROMAN
如果您想按名称迭代,请使用此解决方案:https://dev59.com/snRB5IYBdhLWcg3wZmdz#1759923 - Rodrigo Rodrigues
这里有一篇微软开发者网络(MSDN)的文章,可能对仍然想了解这个问题的人有所帮助:https://msdn.microsoft.com/zh-cn/library/bb613579(v=vs.110).aspx - Greg
5个回答

28

感谢大家的帮助!!我终于搞定了。使用VisualTreeHelper解决了问题。这真是一个很棒的函数^^

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);

辅助函数

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;
    }

哦,它应该在这里:我喜欢这个解决方案,但是如果我有几个文本块,并且我想使第二个或第三个可见/折叠,该怎么办?换句话说,如何进入具有指定名称的列表框控件? - dargod

3

使用此编辑函数,您还可以按名称搜索控件(它是从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;
}

2
使用 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");
  }
}

1
很好,我想使用这个解决方案。然而,显然 FrameworkElement 没有包含“FindVisualChild”的定义。也许原因是我正在使用 Windows Phone 7 的 Silverlight?仍在努力解决这个问题。也许 VisualTreeHelper 可以帮忙 = ) 无论如何,谢谢。 - sust86

2

感谢您对VisualTreeHelper的提示!:) - sust86
感谢我的话,点个赞吧 :) 我真的认为你应该使用SelectedItem样式来实现你的效果,而不是使用代码 - 从长远来看这是最好的方式(我认为)。 - Stuart
1
我试图..."投票需要15个声望" 好的,我肯定会学习如何使用Blend。但是我在Windows Phone编程领域还很新,仍在努力弄清楚所有这些东西是如何工作的。我只需要更多时间=) - sust86
啊 - 抱歉 - 您必须在网站上停留更长时间,然后再回来 :) - Stuart

1

由于DataTemplate是一个通用模板,可以在代码中多次使用,因此无法通过名称(x:Name="numberTextBox")访问它。

我通过创建控件集合来解决类似的问题 - 当Listbox正在填充时,我将Textbox控件添加到集合中。

string text = myCollectionOfTextBoxes[listbox.SelectedIndex].Text; 

直到我找到了更好的解决方案 - 标签属性。在您的ListboxItem中,将标签属性绑定到名称。
Tag="{Binding Name}"

并且访问它的方式

ListBoxItem listBoxItem = ContactListBox.SelectedItem as ListBoxItem;

string name = listBoxItem.Tag.ToString(); 

我不确定我完全理解这种方法。但是只有在我需要知道TextBox/TextBlock的文本时,这才有用。这种解决方案没有办法让TextBox获得焦点并接受用户输入,对吧? - sust86
你想要实现按钮的行为吗?请看这个链接:http://forums.create.msdn.com/forums/t/69801.aspx - Lukasz Madon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接