Reputation: 734
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>
Upvotes: 2
Views: 1415
Reputation: 3631
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>
Upvotes: 5
Reputation: 37059
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>
Upvotes: 2