InputBindings work only when focused

前端 未结 5 1969
旧时难觅i
旧时难觅i 2020-12-03 03:39

I have designed a reuseable usercontrol. It contains UserControl.InputBindings. It is quite simple as it only contains a label and a button (and new properties etc.)

5条回答
  •  醉酒成梦
    2020-12-03 04:16

    We extended Adi Lesters attached behavior code with an unsubscribing mechanism on UnLoaded to clean up the transferred bindings. If the control exits the Visual Tree, the InputBindings are removed from the Window to avoid them being active. (We did not explore using WPF-Triggers on the attached property.)

    As controls get reused by WPF in our solution, the behavior does not detach: Loaded/UnLoaded get called more than once. This does not lead to leaking, as the behavior doesn't hold a reference to the 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().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);
                }
            }
        }
    

    Somehow in our solution some controls are not throwing the UnLoaded event, although they never get used again and even get garbage collected after a while. We are taking care of this with tracking with HashCode/WeakReferences and taking a copy of the InputBindings.

    Full class is:

    public class InputBindingBehavior
    {
        public static readonly DependencyProperty PropagateInputBindingsToWindowProperty =
            DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior),
                new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged));
    
        private static readonly Dictionary, List>> trackedFrameWorkElementsToBindings =
            new Dictionary, List>>();
    
        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.InputBindings.Cast().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, List>> 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);
                }
            }
        }
    }
    

提交回复
热议问题