How to link scrollbar and scrollviewer
I currently have two ScrollViewer's containing alternate views of the same collection. I have bound the scrolling of the two scrollviewers together by handling the ScrollChanged event and using ScrollToVerticalOffset.
For presentation reasons I have set both ScrollViewer scrollbars to hidden and want to control them both from a seperate ScrollBar.
This seems to not be straightforward. I recall seeing a blog about it a few months ago but I can't find it again.
Can anyone point me in the direction of some useful resour开发者_运维问答ces or give me a shove in the right direction to how it might be achieved.
Thanks in advance.
Great, just what I needed. I extended a bit so that the scrollviewer can be set from xaml as well using a dependency property. In xaml:
<local:BindableScrollBar BoundScrollViewer ="{Binding ElementName=ScrollViewer}" Orientation="Vertical" />
Code:
/// <summary>
/// An extended scrollbar that can be bound to an external scrollviewer.
/// </summary>
public class BindableScrollBar : ScrollBar
{
public ScrollViewer BoundScrollViewer
{
get { return (ScrollViewer)GetValue(BoundScrollViewerProperty); }
set { SetValue(BoundScrollViewerProperty, value); }
}
// Using a DependencyProperty as the backing store for BoundScrollViewer. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BoundScrollViewerProperty =
DependencyProperty.Register("BoundScrollViewer", typeof(ScrollViewer), typeof(BindableScrollBar), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnBoundScrollViewerPropertyChanged)));
private static void OnBoundScrollViewerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindableScrollBar sender = d as BindableScrollBar;
if (sender != null && e.NewValue != null)
{
sender.UpdateBindings();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="BindableScrollBar"/> class.
/// </summary>
/// <param name="scrollViewer">The scroll viewer.</param>
/// <param name="o">The o.</param>
public BindableScrollBar(ScrollViewer scrollViewer, Orientation o)
: base()
{
this.Orientation = o;
BoundScrollViewer = scrollViewer;
}
/// <summary>
/// Initializes a new instance of the <see cref="BindableScrollBar"/> class.
/// </summary>
public BindableScrollBar() : base() { }
private void UpdateBindings()
{
AddHandler(ScrollBar.ScrollEvent, new ScrollEventHandler(OnScroll));
BoundScrollViewer.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(BoundScrollChanged));
Minimum = 0;
if (Orientation == Orientation.Horizontal)
{
SetBinding(ScrollBar.MaximumProperty, (new Binding("ScrollableWidth") { Source = BoundScrollViewer, Mode = BindingMode.OneWay }));
SetBinding(ScrollBar.ViewportSizeProperty, (new Binding("ViewportWidth") { Source = BoundScrollViewer, Mode = BindingMode.OneWay }));
}
else
{
this.SetBinding(ScrollBar.MaximumProperty, (new Binding("ScrollableHeight") { Source = BoundScrollViewer, Mode = BindingMode.OneWay }));
this.SetBinding(ScrollBar.ViewportSizeProperty, (new Binding("ViewportHeight") { Source = BoundScrollViewer, Mode = BindingMode.OneWay }));
}
LargeChange = 242;
SmallChange = 16;
}
private void BoundScrollChanged(object sender, ScrollChangedEventArgs e)
{
switch (this.Orientation)
{
case Orientation.Horizontal:
this.Value = e.HorizontalOffset;
break;
case Orientation.Vertical:
this.Value = e.VerticalOffset;
break;
default:
break;
}
}
private void OnScroll(object sender, ScrollEventArgs e)
{
switch (this.Orientation)
{
case Orientation.Horizontal:
this.BoundScrollViewer.ScrollToHorizontalOffset(e.NewValue);
break;
case Orientation.Vertical:
this.BoundScrollViewer.ScrollToVerticalOffset(e.NewValue);
break;
default:
break;
}
}
}
Ok, solved this. Was actually quite straightforward.
Have since found Wpf binding to a function, which should help anyone else interested. Its VB but should be clear enough.
Cheers
Further to above: I subclassed ScrollBar and passed in the ScrollViewer I wanted to bind. Seems to work ok.
public class ScrollViewerBoundScrollBar : ScrollBar
{
private ScrollViewer _scrollViewer;
public ScrollViewer BoundScrollViewer { get { return _scrollViewer; } set { _scrollViewer = value; UpdateBindings(); } }
public ScrollViewerBoundScrollBar( ScrollViewer scrollViewer, Orientation o ) : base()
{
this.Orientation = o;
BoundScrollViewer = _scrollViewer;
}
public ScrollViewerBoundScrollBar() : base()
{
}
private void UpdateBindings()
{
this.AddHandler(ScrollBar.ScrollEvent, new ScrollEventHandler(OnScroll));
_scrollViewer.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(BoundScrollChanged));
this.Minimum = 0;
if (Orientation == Orientation.Horizontal)
{
this.SetBinding(ScrollBar.MaximumProperty, (new Binding("ScrollableWidth") { Source = _scrollViewer, Mode = BindingMode.OneWay }));
this.SetBinding(ScrollBar.ViewportSizeProperty, (new Binding("ViewportWidth") { Source = _scrollViewer, Mode = BindingMode.OneWay }));
}
else
{
this.SetBinding(ScrollBar.MaximumProperty, (new Binding("ScrollableHeight") { Source = _scrollViewer, Mode = BindingMode.OneWay }));
this.SetBinding(ScrollBar.ViewportSizeProperty, (new Binding("ViewportHeight") { Source = _scrollViewer, Mode = BindingMode.OneWay }));
}
this.LargeChange = 242;
this.SmallChange = 16;
}
public void BoundScrollChanged(object sender, ScrollChangedEventArgs e)
{
switch (this.Orientation)
{
case Orientation.Horizontal:
this.Value = e.HorizontalOffset;
break;
case Orientation.Vertical:
this.Value = e.VerticalOffset;
break;
default:
break;
}
}
public void OnScroll(object sender, ScrollEventArgs e)
{
switch(this.Orientation)
{
case Orientation.Horizontal:
this.BoundScrollViewer.ScrollToHorizontalOffset(e.NewValue);
break;
case Orientation.Vertical:
this.BoundScrollViewer.ScrollToVerticalOffset(e.NewValue);
break;
default:
break;
}
}
}
I'm doing something much simpler, here it is:
I have 2 ListViews: Both implement the event "ScrollViewer.ScrollChanged":
<ListView ScrollViewer.ScrollChanged="lvOperations_ScrollChanged" Name="lvPurchases">
<ListView ScrollViewer.ScrollChanged="lvPurchases_ScrollChanged" Name="lvOperations">
private void lvOperations_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViwer = DependencyObjectFunctions.GetChildOfType<ScrollViewer>(lvPurchases) as ScrollViewer;
if (scrollViwer != null)
{
scrollViwer.ScrollToVerticalOffset(e.VerticalOffset);
}
}
private void lvPurchases_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViwer = DependencyObjectFunctions.GetChildOfType<ScrollViewer>(lvOperations) as ScrollViewer;
if (scrollViwer != null)
{
scrollViwer.ScrollToVerticalOffset(e.VerticalOffset);
}
}
Here is the GetChildOfType:
public class DependencyObjectFunctions
{
public static DependencyObject GetChildOfType<T>(DependencyObject o)
{
// Return the DependencyObject if it is a Type T (ScrollViewer in my case)
if (o is T)
{ return o; }
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
{
var child = VisualTreeHelper.GetChild(o, i);
var result = GetChildOfType<T>(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
}
When I scroll one ListView, both are scrolling in sync
精彩评论