2
votes

I have several Expander controls in a wpf usercontrol. When I expand the Expanders I want to automatically scroll so that the whole Expander is visible, if possible. Or atleast so the newly expanded Expander is as far 'up' as possible.

When i do it now on the bottom one it expands below the window edge and I cant see the controls in the expander without manually scrolling to them.

And i want to do this from XAML and not the codebehind. It that possible? Im using a MVVM pattern.

Im assuming that i have to use a Trigger on the Expander but i have no idea how to preform the BringToView functionality

Highlevel layout:

<ScrollViewer>
  <UserControl>
    <Grid>
      <Expander>
        ...
      </Expander>
      <Expander>
        ...
      </Expander>
      <Expander>
        ...
      </Expander>
    </Grid>
  </UserControl>
</ScrollViewer>
2

2 Answers

4
votes

Handle Expanded event and call BringIntoView() method:

private void Expander_Expanded(object sender, RoutedEventArgs e)
{
    ((Expander)sender).BringIntoView();
}

Please note that you should use StackPanel or something else instead of Grid:

<ScrollViewer Name="sv">
    <UserControl>
        <StackPanel>
            <Expander>
                <Border Height="1000" Background="Red"/>
            </Expander>
            <Expander>
                <Border Height="1000" Background="Blue"/>
            </Expander>
            <Expander Expanded="Expander_Expanded">
                <Border Height="1000" Background="Green"/>
            </Expander>
        </StackPanel>
    </UserControl>
</ScrollViewer>

Edit

You might want to use this attached property:

public static class ExpanderEx
{
    public static readonly DependencyProperty BringIntoViewOnExpandProperty =
                           DependencyProperty.RegisterAttached("BringIntoViewOnExpand", 
                                typeof(bool), typeof(ExpanderEx), 
                                new PropertyMetadata(false, OnBringIntoViewOnExpandChanged));


    public static bool GetBringIntoViewOnExpand(DependencyObject obj)
    {
        return (bool)obj.GetValue(BringIntoViewOnExpandProperty);
    }
    public static void SetBringIntoViewOnExpand(DependencyObject obj, bool value)
    {
        obj.SetValue(BringIntoViewOnExpandProperty, value);
    }  
    private static void OnBringIntoViewOnExpandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Expander)
        {
            Expander obj = (Expander)d;
            if (e.NewValue.Equals(true)) 
                obj.Expanded += Obj_Expanded; 
            else
                obj.Expanded -= Obj_Expanded;
        }
    }

    private static void Obj_Expanded(object sender, RoutedEventArgs e)
    {
        ((Expander)sender).BringIntoView();
    }
}

and in Xaml:

<ScrollViewer Name="sv">
    <UserControl>
        <StackPanel>
            <Expander local:ExpanderEx.BringIntoViewOnExpand="True">
                <Border Height="1000" Background="Red"/>
            </Expander>
            <Expander local:ExpanderEx.BringIntoViewOnExpand="True">
                <Border Height="1000" Background="Blue"/>
            </Expander>
            <Expander local:ExpanderEx.BringIntoViewOnExpand="True">
                <Border Height="1000" Background="Green"/>
            </Expander>
        </StackPanel>
    </UserControl>
</ScrollViewer>
2
votes

Give each Expander this handler for its Expanded event. It looks like you're defining the expanders inside a UserControl, so getting to the ScrollViewer is a problem. One option is to give your UserControl a ScrollViewer dependency property that the owner will bind to whatever ScrollViewer contains it. I don't like that. Instead, I wrote a helper function that looks up the visual tree and finds the nearest parent ScrollViewer, if any.

private void Expander_Expanded(object sender, RoutedEventArgs e)
{
    var scrollViewer = GetNearestScrollViewerParent();

    if (scrollViewer == null)
        return;

    var expander = (Expander)sender;

    UIElement container = VisualTreeHelper.GetParent(expander) as UIElement;
    Point relativeLocation = expander.TranslatePoint(new Point(0, 0), container);

    scrollViewer.ScrollToVerticalOffset(relativeLocation.Y);
}

public ScrollViewer GetNearestScrollViewerParent()
{
    for (var parent = VisualTreeHelper.GetParent(this); 
         parent != null; 
         parent = VisualTreeHelper.GetParent(parent))
    {
        if (parent is ScrollViewer)
            return parent as ScrollViewer;
    }

    return null;
}

XAML:

<UserControl.Resources>
    <Style TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
        <EventSetter Event="Expanded" Handler="Expander_Expanded" />
    </Style>
</UserControl.Resources>