LoginSignup
2
4

More than 5 years have passed since last update.

ScrollViewerのスクロール位置を同期する

Last updated at Posted at 2018-12-27

やりたいこと

複数のScrollViewerのスクロール位置を同期させる。ただし、同期する相手をグループに分けられるようにする。

実装

スクロール位置を同期させるビヘイビアを作成する。

ビヘイビア
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Sample {
    public class ScrollSynchronizer {
        //ScrollViewerとグループ名の対応リスト
        private static Dictionary<ScrollViewer, string> _scrollViewers 
            = new Dictionary<ScrollViewer, string>();

        //水平方向のスクロール位置のリスト
        private static Dictionary<string, double> _horizontalScrollOffsets 
            = new Dictionary<string, double>();

        //垂直方向のスクロール位置のリスト
        private static Dictionary<string, double> _verticalScrollOffsets 
            = new Dictionary<string, double>();

        //グループ名の添付プロパティ
        public static readonly DependencyProperty ScrollGroupProperty 
            = DependencyProperty.RegisterAttached(
                "ScrollGroup",
                typeof(string),
                typeof(ScrollSynchronizer),
                new PropertyMetadata(new PropertyChangedCallback(OnScrollGroupChanged))
            );

        public static void SetScrollGroup(DependencyObject obj, string scrollGroup)
            => obj.SetValue(ScrollGroupProperty, scrollGroup);

        public static string GetScrollGroup(DependencyObject obj)
            => (string)obj.GetValue(ScrollGroupProperty);

        private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var scrollViewer = d as ScrollViewer;

            if (scrollViewer == null)
                return;

            var oldGroup = (string)e.OldValue ?? "";
            var newGroup = (string)e.NewValue ?? "";

            if (oldGroup != "") {
                if (_scrollViewers.ContainsKey(scrollViewer)) {
                    //登録解除
                    scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
                    _scrollViewers.Remove(scrollViewer);
                }
            }

            if (newGroup != "") {
                if (_scrollViewers.ContainsValue(newGroup)) {
                    //既存のグループ名の場合は、現在のスクロール位置を反映する
                    scrollViewer.ScrollToHorizontalOffset(_horizontalScrollOffsets[newGroup]);
                    scrollViewer.ScrollToVerticalOffset(_verticalScrollOffsets[newGroup]);
                } else {
                    //新しいグループ名の場合は、スクロール位置を記録する
                    _horizontalScrollOffsets[newGroup] = scrollViewer.HorizontalOffset;
                    _verticalScrollOffsets[newGroup] = scrollViewer.VerticalOffset;
                }

                //ScrollViewerを登録
                _scrollViewers.Add(scrollViewer, newGroup);
                scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
            }
        }

        private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) {
            var changedScrollViewer = sender as ScrollViewer;
            var group = _scrollViewers[changedScrollViewer];
            var scrollViewers = _scrollViewers.Where(s => s.Value == group && s.Key != changedScrollViewer);

            //垂直方向
            if (e.VerticalChange != 0) {
                _verticalScrollOffsets[group] = changedScrollViewer.VerticalOffset;

                //同じグループのScrollViewerにスクロール位置を反映
                scrollViewers
                    .Where(s => s.Key.VerticalOffset != _verticalScrollOffsets[group])
                    .Select(x => x.Key)
                    .ForEach(scrollViewer => scrollViewer.ScrollToVerticalOffset(_verticalScrollOffsets[group]));
            }

            //水平方向
            if (e.HorizontalChange != 0) {
                _horizontalScrollOffsets[group] = changedScrollViewer.HorizontalOffset;

                //同じグループのScrollViewerにスクロール位置を反映
                scrollViewers
                    .Where(s => s.Key.HorizontalOffset != _horizontalScrollOffsets[group])
                    .Select(x => x.Key)
                    .ForEach(scrollViewer => scrollViewer.ScrollToHorizontalOffset(_horizontalScrollOffsets[group]));
            }
        }
    }
}

使い方

XAML
<Window x:Class="Sample.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Sample"
        Title="MainView" Height="300" Width="300">
    <DockPanel Orientation="Vertical">
        <!--同期させたいScrollViewerに同じグループ名を指定する-->
        <ScrollViewer local:ScrollSynchronizer.ScrollGroup="Group1">
            <StackPanel>
                <Button Content="Button1" />
                <!--中略-->
                <Button Content="Button15" />
            </StackPanel>
        </ScrollViewer>
        <ScrollViewer local:ScrollSynchronizer.ScrollGroup="Group1">
            <StackPanel>
                <Button Content="Button1" />
                <!--中略-->
                <Button Content="Button15" />
            </StackPanel>
        </ScrollViewer>
    </DockPanel>
</Window>

注意事項

スタイルで使用する場合は、ScrollViewerが入れ子になっていないか注意する。入れ子にしていないつもりでも、DataGridやComboBoxはScrollViewerを内蔵しているので、思わぬ影響が出ることがある。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4