16

I have a ListBox with an ItemsPanel

<Setter Property="ItemsPanel">
    <Setter.Value>
        <ItemsPanelTemplate>
             <StackPanel x:Name="ThumbListStack" Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </Setter.Value>
</Setter>

I am wanting to move the Stack Panel along the X-axis using a TranslateTransform in code behind.

Problem is, I can't find the Stack Panel.

ThumbListBox.FindName("ThumbListStack")

Returns nothing. I want to use it in:

Storyboard.SetTarget(x, ThumbListBox.FindName("ThumbListStack"))

How do I get the Stack Panel so I can then use it with the TranslateTransform

Thanks

sll
  • 61,540
  • 22
  • 104
  • 156
Ben
  • 1,000
  • 2
  • 15
  • 36
  • If this is just a single `ListBox` you can use the `Loaded` event for the `StackPanel` and then store it in code behind – Fredrik Hedblad Sep 06 '11 at 12:54
  • Hi Meleak, how do I attach to the Loaded event for the StackPanel? I can't see it as an option in VS – Ben Sep 06 '11 at 13:41
  • I added an answer of how you can use the `Loaded` event which will work if your style is defined in the same View as the `Listbox`. Otherwise you can use the second part of my answer which search the Visual Tree – Fredrik Hedblad Sep 06 '11 at 13:55

3 Answers3

27

You can use the Loaded event for the StackPanel that is in the ItemsPanelTemplate

<Grid>
    <Grid.Resources>
        <Style TargetType="ListBox">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <StackPanel x:Name="ThumbListStack" Orientation="Horizontal"
                                    Loaded="StackPanel_Loaded" />
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Resources>
    <ListBox />
</Grid>

And then in code behind

private StackPanel m_itemsPanelStackPanel;
private void StackPanel_Loaded(object sender, RoutedEventArgs e)
{
    m_itemsPanelStackPanel = sender as StackPanel;
}

Another way is to traverse the Visual Tree and find the StackPanel which will be the first child of the ItemsPresenter.

public void SomeMethod()
{
    ItemsPresenter itemsPresenter = GetVisualChild<ItemsPresenter>(listBox);
    StackPanel itemsPanelStackPanel = GetVisualChild<StackPanel>(itemsPresenter);
}

private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
    T child = default(T);

    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
        if (child != null)
        {
            break;
        }
    }
    return child;
}
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • 2
    Better to rename GetVisualChild() to GetFirstVisualChild() – sll Sep 06 '11 at 13:59
  • @Ben: Good to hear it worked, glad to help :) Just so you know, now that you have over 15 in reputation score you're allowed to upvote the answers which you find helpful. – Fredrik Hedblad Sep 06 '11 at 14:25
  • This could also be done in an [Initialized](http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.initialized.aspx) event handler. The member variable will then be set during the initialization phase and already be valid e.g. when the Loaded event of the parent control or window is raised. – Clemens Oct 30 '12 at 11:44
3

sorry I just noticed I forgot to save the edit...I realize you've already accepted an answer, but it seems more of a hack to me. Here's my implementation of FindChild, you might want to use it for the future or if you're going to be doing this often.

public static T FindChild<T>(this FrameworkElement obj, string name)
{
    DependencyObject dep = obj as DependencyObject;
    T ret = default(T);

    if (dep != null)
    {
        int childcount = VisualTreeHelper.GetChildrenCount(dep);
        for (int i = 0; i < childcount; i++)
        {
            DependencyObject childDep = VisualTreeHelper.GetChild(dep, i);
            FrameworkElement child = childDep as FrameworkElement;

            if (child.GetType() == typeof(T) && child.Name == name)
            {
                ret = (T)Convert.ChangeType(child, typeof(T));
                break;
            }

            ret = child.FindChild<T>(name);
            if (ret != null)
                break;
        }
    }
    return ret;
}

It checks all the children and the children's children comparing the type and Name set on the control. Use it like this:

StackPanel ipanel = ThumbListBox.FindChild<StackPanel>("ThumbListStack");
if(ipanel == null)
    MessageBox.Show("couldn't find anything");
else
    MessageBox.Show("Aha! Found: " ipanel.Name);
Saad Imran.
  • 4,480
  • 2
  • 23
  • 33
  • Hi Saad, Thanks for the reply. I'm still early days with WPF, but my interpretation of FindParent is it will go up the tree, not down. Isn't the ItemsPanel down the tree? i.e. the StackPanel is a child of ThumbListBox? – Ben Sep 06 '11 at 12:45
  • Hey Ben, sorry I misunderstood the first time. You're right, the StackPanel is a child of the ThumbListBox. I updated my answer. – Saad Imran. Sep 06 '11 at 13:21
  • Hi Saad, I can't see any changes to your answer? – Ben Sep 06 '11 at 13:40
  • @Ben Hey, sorry. I forgot to save my edit. I saved it now, you might still want to use it or maybe for the future. – Saad Imran. Sep 06 '11 at 14:29
1

Try out following extension method:

var childStackPanels = FindVisualChildren<StackPanel>(ThumbListBox);

Method itself:

public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            var typedChild = child as T;
            if (typedChild != null)
            {
                yield return typedChild;
            }    

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

PS: You can yourself extend it to check for specific control name so method would return single control instead of list.

sll
  • 61,540
  • 22
  • 104
  • 156