Skip to content

Instantly share code, notes, and snippets.

@punker76
Created July 30, 2018 14:15
Show Gist options
  • Save punker76/7919c21b4b917972e39f8a58da6a96a6 to your computer and use it in GitHub Desktop.
Save punker76/7919c21b4b917972e39f8a58da6a96a6 to your computer and use it in GitHub Desktop.
ListBox scroll syncronisation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace ListBoxScrollSync
{
public static class ScrollSync
{
/// <summary>
/// List of all registered scroll viewers.
/// </summary>
private static readonly Dictionary<ScrollViewer, string> scrollViewers = new Dictionary<ScrollViewer, string>();
/// <summary>
/// Contains the latest horizontal scroll offset for each scroll group.
/// </summary>
private static readonly Dictionary<string, double> horizontalScrollOffsets = new Dictionary<string, double>();
/// <summary>
/// Contains the latest vertical scroll offset for each scroll group.
/// </summary>
private static readonly Dictionary<string, double> verticalScrollOffsets = new Dictionary<string, double>();
/// <summary>
/// Identifies the attached property ScrollGroup
/// </summary>
public static readonly DependencyProperty ScrollGroupProperty
= DependencyProperty.RegisterAttached(
"ScrollGroup",
typeof(string),
typeof(ScrollSync),
new PropertyMetadata(OnScrollGroupChanged));
/// <summary>
/// Sets the value of the attached property ScrollGroup.
/// </summary>
/// <param name="obj">Object on which the property should be applied.</param>
/// <param name="scrollGroup">Value of the property.</param>
public static void SetScrollGroup(DependencyObject obj, string scrollGroup)
{
obj.SetValue(ScrollGroupProperty, scrollGroup);
}
/// <summary>
/// Gets the value of the attached property ScrollGroup.
/// </summary>
/// <param name="obj">Object for which the property should be read.</param>
/// <returns>Value of the property StartTime</returns>
public static string GetScrollGroup(DependencyObject obj)
{
return (string) obj.GetValue(ScrollGroupProperty);
}
private static T GetDescendant<T>(DependencyObject element) where T : Visual
{
if (element == null)
{
return default(T);
}
if (element is T)
{
return (T) element;
}
(element as FrameworkElement)?.ApplyTemplate();
T foundElement = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var nextChild = VisualTreeHelper.GetChild(element, i);
foundElement = GetDescendant<T>(nextChild);
if (foundElement != null)
{
break;
}
}
return foundElement;
}
/// <summary>
/// Occurs, when the ScrollGroupProperty has changed.
/// </summary>
/// <param name="d">The DependencyObject on which the property has changed value.</param>
/// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollGroupChangedAction = new Action<ScrollViewer>((sv) =>
{
if (sv != null)
{
if (!string.IsNullOrEmpty((string) e.OldValue))
{
// Remove scrollviewer
if (scrollViewers.ContainsKey(sv))
{
sv.ScrollChanged -= ScrollViewer_ScrollChanged;
scrollViewers.Remove(sv);
}
}
if (!string.IsNullOrEmpty((string) e.NewValue))
{
// If group already exists, set scrollposition of new scrollviewer to the scrollposition of the group
if (horizontalScrollOffsets.Keys.Contains((string) e.NewValue))
{
sv.ScrollToHorizontalOffset(horizontalScrollOffsets[(string) e.NewValue]);
}
else
{
horizontalScrollOffsets.Add((string) e.NewValue, sv.HorizontalOffset);
}
if (verticalScrollOffsets.Keys.Contains((string) e.NewValue))
{
sv.ScrollToVerticalOffset(verticalScrollOffsets[(string) e.NewValue]);
}
else
{
verticalScrollOffsets.Add((string) e.NewValue, sv.VerticalOffset);
}
// Add scrollviewer
scrollViewers.Add(sv, (string) e.NewValue);
sv.ScrollChanged += ScrollViewer_ScrollChanged;
}
}
});
var listBox = d as ListBox;
var scrollViewer = GetDescendant<ScrollViewer>(listBox);
if (scrollViewer != null)
{
scrollGroupChangedAction(scrollViewer);
}
else
{
listBox?.Dispatcher?.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => { scrollGroupChangedAction(GetDescendant<ScrollViewer>(listBox)); }));
}
}
/// <summary>
/// Occurs, when the scroll offset of one scrollviewer has changed.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">EventArgs of the event.</param>
private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var changedScrollViewer = (ScrollViewer) sender;
if (e.VerticalChange != 0 && (changedScrollViewer.VerticalScrollBarVisibility == ScrollBarVisibility.Auto || changedScrollViewer.VerticalScrollBarVisibility == ScrollBarVisibility.Visible))
{
Scroll(changedScrollViewer);
}
else if (e.HorizontalChange != 0 && (changedScrollViewer.HorizontalScrollBarVisibility == ScrollBarVisibility.Auto || changedScrollViewer.HorizontalScrollBarVisibility == ScrollBarVisibility.Visible))
{
Scroll(changedScrollViewer);
}
}
/// <summary>
/// Scrolls all scroll viewers of a group to the position of the selected scroll viewer.
/// </summary>
/// <param name="changedScrollViewer">Sroll viewer, that specifies the current position of the group.</param>
private static void Scroll(ScrollViewer changedScrollViewer)
{
var group = scrollViewers[changedScrollViewer];
verticalScrollOffsets[group] = changedScrollViewer.VerticalOffset;
horizontalScrollOffsets[group] = changedScrollViewer.HorizontalOffset;
foreach (var scrollViewer in scrollViewers.Where((s) => s.Value == group && s.Key != changedScrollViewer))
{
scrollViewer.Key.ScrollChanged -= ScrollViewer_ScrollChanged;
if (scrollViewer.Key.VerticalOffset != changedScrollViewer.VerticalOffset)
{
scrollViewer.Key.ScrollToVerticalOffset(changedScrollViewer.VerticalOffset);
}
if (scrollViewer.Key.HorizontalOffset != changedScrollViewer.HorizontalOffset)
{
scrollViewer.Key.ScrollToHorizontalOffset(changedScrollViewer.HorizontalOffset);
}
scrollViewer.Key.ScrollChanged += ScrollViewer_ScrollChanged;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment