Question

I'm trying to add behavior to my ItemsPanelTemplate where all the items are collapsed except the one on top hence I've specified a StackPanel with a custom attached behavior.

<ItemsControl ItemsSource="{Binding ViewModels}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel my:ElementUtilities.CollapseAllButLast="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

The problem is that when the LayoutUpdated event handler is called within my behavior the sender is always null. Why is this?

public static class ElementUtilities
{
    public static readonly DependencyProperty CollapseAllButLastProperty = DependencyProperty.RegisterAttached
        ("CollapseAllButLast", typeof(bool), typeof(ElementUtilities), new PropertyMetadata(false, CollapseAllButLastChanged));

    static void CollapseAllButLastChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        StackPanel sp = o as StackPanel;
        if (sp != null)
        {
            if (e.NewValue != null && (bool)e.NewValue)
                sp.LayoutUpdated += sp_LayoutUpdated;
            else
                sp.LayoutUpdated -= sp_LayoutUpdated;
        }
        else
            throw new InvalidOperationException("The attached CollapseAllButLast property can only be applied to StackPanel instances.");
    }

    public static bool GetCollapseAllButLast(StackPanel stackPanel)
    {
        if (stackPanel == null)
            throw new ArgumentNullException("stackPanel");
        return (bool)stackPanel.GetValue(CollapseAllButLastProperty);
    }

    public static void SetCollapseAllButLast(StackPanel stackPanel, bool collapseAllButLast)
    {
        if (stackPanel == null)
            throw new ArgumentNullException("stackPanel");
        stackPanel.SetValue(CollapseAllButLastProperty, collapseAllButLast);
    }

    static void sp_LayoutUpdated(object sender, EventArgs e)
    {
        // Collapse all but last element
        StackPanel sp = (StackPanel)sender;     // This is always null
        for (int i = 0; i < sp.Children.Count - 1; i++)
        {
            UIElement l = sp.Children[i];
            l.Visibility = Visibility.Collapsed;
        }
        sp.Children[sp.Children.Count - 1].Visibility = Visibility.Visible;
    }
}
Was it helpful?

Solution

As per MSDN documentation of LayoutUpdated event -

When you handle LayoutUpdated, do not rely on the sender value. For LayoutUpdated, sender is always null, regardless of where the handler is attached. This is to prevent handlers from assigning any meaning to sender, such as implying that it was that specific element that raised the event out of the visual tree.

Instead you can hook to loaded event -

if (e.NewValue != null && (bool)e.NewValue)
   sp.Loaded += sp_Loaded;
else
   sp.Loaded -= sp_Loaded;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top