14

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;

    }
Kara
  • 6,115
  • 16
  • 50
  • 57
sust86
  • 1,870
  • 2
  • 18
  • 25
  • 1
    I like the solution, but what if I have few textblocks and I would like to make visible/collapsed not the first but ex. 2nd or 3rd? In the other words how to get into listbox control with specified name? – dargod Aug 29 '12 at 14:36
  • @sust86 What is **XXX** in listBoxTemplate.XXX? – ROMAN Feb 27 '14 at 12:58
  • If you want iterate by name, use this solution: http://stackoverflow.com/a/1759923/3934111 – Rodrigo Rodrigues May 07 '15 at 21:14
  • Here is an MSDN article that might also help explain this stuff to anyone still wondering: https://msdn.microsoft.com/en-us/library/bb613579(v=vs.110).aspx – Greg Feb 06 '17 at 00:39

5 Answers5

28

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;
    }
sust86
  • 1,870
  • 2
  • 18
  • 25
  • oh it should be here: I like the solution, but what if I have few textblocks and I would like to make visible/collapsed not the first but ex. 2nd or 3rd? In the other words how to get into listbox control with specified name? – dargod Aug 29 '12 at 14:41
3

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;
}
SZL
  • 805
  • 8
  • 12
2

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/

Stuart
  • 66,722
  • 7
  • 114
  • 165
  • Thank you for your hint with the VisualTreeHelper ! :) – sust86 Mar 04 '11 at 09:08
  • To thank me... hit the upvote :) And I really do think you should do your effect using the SelectedItem style rather than using code - long term its the best way (I think) – Stuart Mar 04 '11 at 09:32
  • 1
    I tried to... "Vote Up requires 15 reputation" Well, im definitely going to learn how to work with Blend. But im new in the windows phone programming scene and still trying to figure out how all this stuff does work. I just need more time = ) – sust86 Mar 04 '11 at 16:42
  • ah - sorry - you'll have to stay on the site longer and then come back :) – Stuart Mar 04 '11 at 16:47
2

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");
  }
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • 1
    Great, i would like to use this solution. However, apparently a FrameworkElement does not contain a definition for "FindVisualChild". Maybe the reason for this is that i'm working with silverlight for windows phone 7?! Still trying to figure out how to solve this problem. Maybe the VisualTreeHelper can help = ) TY anyway – sust86 Mar 03 '11 at 15:35
1

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(); 
Lukasz Madon
  • 14,664
  • 14
  • 64
  • 108
  • I am not sure I understand this approach completely. But this would only be useful when i need to know the Text of the TextBox / TextBlock. There is no way to focus the TextBox and accept user input with this solution, right? – sust86 Mar 03 '11 at 15:24
  • So you would like to have the behaviour of Button? http://forums.create.msdn.com/forums/t/69801.aspx take a look at this. – Lukasz Madon Mar 03 '11 at 16:10