Reputation: 2051
I have a ScrollViewer
on a WPF user control and what I'd like to achieve is to show a shadow image at the top and bottom of the ScrollViewer
, but hide the top shadow when the scrollbar is at the top, and hide the bottom shadow when the scrollbar is at the bottom.
In other words, I need to bind the Visibility
property of the image to the offset of the ScrollViewer
somehow. The following code is clearly not right but should illustrate what I'm trying to do.
<Grid>
<Image Source="Shadow.png" VerticalAlignment="Top">
<Image.Resources>
<Style TargetType="Image">
<Style.Triggers>
<Trigger SourceName="Scroller" Property="VerticalOffset" Value="GREATER THAN ZERO OR LESS THAN MAX">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Resources>
</Image>
<ScrollViewer Height="200" x:Name="Scroller">
<ContentControl />
</ScrollViewer>
</Grid>
Upvotes: 2
Views: 673
Reputation: 1823
Here is what I would do:
First, you'll need an IMultiValueConverter:
public class ScrollOffsetToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null)
throw new ArgumentException("Values cannot be null.");
if (values.Count() != 2)
throw new ArgumentException("Incorrect number of bindings (" + values.Count() + ")");
if (parameter == null)
throw new ArgumentException("Parameter cannot be null.");
var top = parameter.ToString().ToUpper() == "TOP";
var offset = Double.Parse(values[0].ToString());
var maxHeight = Double.Parse(values[1].ToString());
return (top && offset == 0) || (!top && offset == maxHeight) ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then, you can use this converter to apply a Setter for the Visibility property of your image.
<Image.Style Source="Shadow.png" VerticalAlignment="Top">
<Style>
<Setter Property="Image.Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource ScrollOffsetToVisibilityConverter}" ConverterParameter="Top">
<Binding ElementName="Scroller" Path="VerticalOffset"/>
<Binding ElementName="Scroller" Path="ScrollableHeight"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Image.Style>
Just pass in "top" or "bottom" (or more accurately, not "top") for the ConverterParameter to return "Visible" if the scroll bar is at the top or bottom.
Upvotes: 2
Reputation: 17085
You have to use ScrollBar
instead of ScrollViewer
because ScrollViewer
can't tell when it reaches the end of its content. (or at least I haven't seen a reliable method for doing so). On the other hand ScrollBar
is more suitable for these kinds of operations.
I used a ListBox with several items to test this behavior. you can change it to whatever you need.
Xaml :
<Grid>
<ListBox ScrollBar.Scroll="ListBox_Scroll">
<!-- add many items here -->
<TextBlock Text="something"/>
</ListBox>
<Image Source="Shadow.png" VerticalAlignment="Top">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding ScrollerState}"
Value="{x:Static enum:ScrollState.AtTheTop}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Source="Shadow.png" VerticalAlignment="Bottom">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding ScrollerState}"
Value="{x:Static enum:ScrollState.AtTheBottom}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
As you can see I changed the trigger to DataTrigger
and used a bindable property in DataContext (named ScrollerState
with type of ScrollState
which is a simple enum) to bind to.
enum:
public enum ScrollState
{
AtTheTop, AtTheBottom, InBetween
}
Now in Code behind we implement the Scroll event from where we change the value of ScrollerState
:
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
private void ListBox_Scroll(object sender, ScrollEventArgs e)
{
ScrollBar sb = e.OriginalSource as ScrollBar;
if (sb.Orientation == Orientation.Horizontal)
return;
if (sb.Value == 0)
(DataContext as VM).ScrollerState = ScrollState.AtTheTop;
else if (sb.Value == sb.Maximum)
(DataContext as VM).ScrollerState = ScrollState.AtTheBottom;
else
(DataContext as VM).ScrollerState = ScrollState.InBetween;
}
VM (an instance of this is set as DataContext of the window):
public class VM : DependencyObject
{
public ScrollState ScrollerState
{
get { return (ScrollState)GetValue(ScrollerStateProperty); }
set { SetValue(ScrollerStateProperty, value); }
}
public static readonly DependencyProperty ScrollerStateProperty =
DependencyProperty.Register("ScrollerState", typeof(ScrollState), typeof(VM),
new PropertyMetadata(ScrollState.AtTheTop));
}
Upvotes: 0