开发者

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜