我们在UnLoaded上使用取消订阅机制扩展了Adi Lesters附加的行为代码,以清理传输的绑定。如果控件退出可视树,则 InputBindings 将从窗口中删除以避免它们处于活动状态。(我们没有探索在附加属性上使用 WPF-Triggers。)
由于控件在我们的解决方案中被 WPF 重用,因此行为不会分离:多次调用Loaded / UnLoaded。这不会导致泄漏,因为该行为不包含对 FrameWorkElement 的引用。
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
((FrameworkElement)d).Unloaded += OnFrameworkElementUnLoaded;
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
if (window != null)
{
// transfer InputBindings into our control
if (!trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement, out var bindingList))
{
bindingList = frameworkElement.InputBindings.Cast<InputBinding>().ToList();
trackedFrameWorkElementsToBindings.Add(
frameworkElement, bindingList);
}
// apply Bindings to Window
foreach (var inputBinding in bindingList)
{
window.InputBindings.Add(inputBinding);
}
frameworkElement.InputBindings.Clear();
}
}
private static void OnFrameworkElementUnLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
// remove Bindings from Window
if (window != null)
{
if (trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement, out var bindingList))
{
foreach (var binding in bindingList)
{
window.InputBindings.Remove(binding);
frameworkElement.InputBindings.Add(binding);
}
trackedFrameWorkElementsToBindings.Remove(frameworkElement);
}
}
}
不知何故,在我们的解决方案中,一些控件没有抛出UnLoaded事件,尽管它们再也不会被使用,甚至会在一段时间后被垃圾收集。我们通过使用 HashCode/WeakReferences 跟踪并获取 InputBindings 的副本来处理这个问题。
全班是:
public class InputBindingBehavior
{
public static readonly DependencyProperty PropagateInputBindingsToWindowProperty =
DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior),
new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged));
private static readonly Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>> trackedFrameWorkElementsToBindings =
new Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>>();
public static bool GetPropagateInputBindingsToWindow(FrameworkElement obj)
{
return (bool)obj.GetValue(PropagateInputBindingsToWindowProperty);
}
public static void SetPropagateInputBindingsToWindow(FrameworkElement obj, bool value)
{
obj.SetValue(PropagateInputBindingsToWindowProperty, value);
}
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
((FrameworkElement)d).Unloaded += OnFrameworkElementUnLoaded;
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
if (window != null)
{
// transfer InputBindings into our control
if (!trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement.GetHashCode(), out var trackingData))
{
trackingData = Tuple.Create(
new WeakReference<FrameworkElement>(frameworkElement),
frameworkElement.InputBindings.Cast<InputBinding>().ToList());
trackedFrameWorkElementsToBindings.Add(
frameworkElement.GetHashCode(), trackingData);
}
// apply Bindings to Window
foreach (var inputBinding in trackingData.Item2)
{
window.InputBindings.Add(inputBinding);
}
frameworkElement.InputBindings.Clear();
}
}
private static void OnFrameworkElementUnLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
var hashCode = frameworkElement.GetHashCode();
// remove Bindings from Window
if (window != null)
{
if (trackedFrameWorkElementsToBindings.TryGetValue(hashCode, out var trackedData))
{
foreach (var binding in trackedData.Item2)
{
frameworkElement.InputBindings.Add(binding);
window.InputBindings.Remove(binding);
}
trackedData.Item2.Clear();
trackedFrameWorkElementsToBindings.Remove(hashCode);
// catch removed and orphaned entries
CleanupBindingsDictionary(window, trackedFrameWorkElementsToBindings);
}
}
}
private static void CleanupBindingsDictionary(Window window, Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>> bindingsDictionary)
{
foreach (var hashCode in bindingsDictionary.Keys.ToList())
{
if (bindingsDictionary.TryGetValue(hashCode, out var trackedData) &&
!trackedData.Item1.TryGetTarget(out _))
{
Debug.WriteLine($"InputBindingBehavior: FrameWorkElement {hashCode} did never unload but was GCed, cleaning up leftover KeyBindings");
foreach (var binding in trackedData.Item2)
{
window.InputBindings.Remove(binding);
}
trackedData.Item2.Clear();
bindingsDictionary.Remove(hashCode);
}
}
}
}