我们在
Adi Lesters的附加行为代码中添加了一个取消订阅机制,以清理传输的绑定,当控件退出可视树时,输入绑定将从窗口中移除,以避免它们处于活动状态。(我们没有探索在附加属性上使用WPF触发器。)
由于在我们的解决方案中,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)
{
if (!trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement, out var bindingList))
{
bindingList = frameworkElement.InputBindings.Cast<InputBinding>().ToList();
trackedFrameWorkElementsToBindings.Add(
frameworkElement, bindingList);
}
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);
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)
{
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);
}
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();
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);
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);
}
}
}
}
<KeyBinding Gesture="CTRL+S" Command="{Binding SaveCommand, Source={StaticResource Locator}}" CommandParameter="{Binding}" />
- John