167 lines
4.4 KiB
C#
167 lines
4.4 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
|
|
namespace EmbyToolbox.Behaviors;
|
|
|
|
public static class TreeViewScrollSyncBehavior
|
|
{
|
|
public static readonly DependencyProperty SyncGroupProperty =
|
|
DependencyProperty.RegisterAttached(
|
|
"SyncGroup",
|
|
typeof(string),
|
|
typeof(TreeViewScrollSyncBehavior),
|
|
new PropertyMetadata(string.Empty, OnSyncGroupChanged));
|
|
|
|
private static readonly Dictionary<string, List<WeakReference<TreeView>>> Groups = new(StringComparer.Ordinal);
|
|
private static bool _syncing;
|
|
|
|
public static string GetSyncGroup(DependencyObject obj) => (string)obj.GetValue(SyncGroupProperty);
|
|
public static void SetSyncGroup(DependencyObject obj, string value) => obj.SetValue(SyncGroupProperty, value);
|
|
|
|
private static void OnSyncGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is not TreeView treeView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
treeView.Loaded -= TreeViewOnLoaded;
|
|
treeView.Unloaded -= TreeViewOnUnloaded;
|
|
|
|
if (!string.IsNullOrWhiteSpace(e.NewValue as string))
|
|
{
|
|
treeView.Loaded += TreeViewOnLoaded;
|
|
treeView.Unloaded += TreeViewOnUnloaded;
|
|
}
|
|
}
|
|
|
|
private static void TreeViewOnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is not TreeView tree)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var group = GetSyncGroup(tree);
|
|
if (string.IsNullOrWhiteSpace(group))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Groups.TryGetValue(group, out var list))
|
|
{
|
|
list = new List<WeakReference<TreeView>>();
|
|
Groups[group] = list;
|
|
}
|
|
|
|
list.Add(new WeakReference<TreeView>(tree));
|
|
|
|
var scroll = FindScrollViewer(tree);
|
|
if (scroll is not null)
|
|
{
|
|
scroll.ScrollChanged -= OnScrollChanged;
|
|
scroll.ScrollChanged += OnScrollChanged;
|
|
}
|
|
}
|
|
|
|
private static void TreeViewOnUnloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is not TreeView tree)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var scroll = FindScrollViewer(tree);
|
|
if (scroll is not null)
|
|
{
|
|
scroll.ScrollChanged -= OnScrollChanged;
|
|
}
|
|
}
|
|
|
|
private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
|
|
{
|
|
if (_syncing || e.VerticalChange == 0 || sender is not ScrollViewer sourceScroll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var sourceTree = FindAncestor<TreeView>(sourceScroll);
|
|
if (sourceTree is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var group = GetSyncGroup(sourceTree);
|
|
if (string.IsNullOrWhiteSpace(group) || !Groups.TryGetValue(group, out var members))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_syncing = true;
|
|
foreach (var weak in members.ToList())
|
|
{
|
|
if (!weak.TryGetTarget(out var targetTree))
|
|
{
|
|
members.Remove(weak);
|
|
continue;
|
|
}
|
|
|
|
if (ReferenceEquals(targetTree, sourceTree))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var targetScroll = FindScrollViewer(targetTree);
|
|
if (targetScroll is not null)
|
|
{
|
|
targetScroll.ScrollToVerticalOffset(sourceScroll.VerticalOffset);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_syncing = false;
|
|
}
|
|
}
|
|
|
|
private static ScrollViewer? FindScrollViewer(DependencyObject root)
|
|
{
|
|
if (root is ScrollViewer sv)
|
|
{
|
|
return sv;
|
|
}
|
|
|
|
var count = VisualTreeHelper.GetChildrenCount(root);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var child = VisualTreeHelper.GetChild(root, i);
|
|
var found = FindScrollViewer(child);
|
|
if (found is not null)
|
|
{
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static T? FindAncestor<T>(DependencyObject node) where T : DependencyObject
|
|
{
|
|
var current = node;
|
|
while (current is not null)
|
|
{
|
|
if (current is T t)
|
|
{
|
|
return t;
|
|
}
|
|
|
|
current = VisualTreeHelper.GetParent(current);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|