Prechádzať zdrojové kódy

Merge branch 'master' into spellchecker

Jumar Macato 7 rokov pred
rodič
commit
3c2a40a2b0

+ 7 - 0
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Linq;
+using System.Reactive.Disposables;
 using System.Runtime.CompilerServices;
 using Avalonia.Collections;
 using Avalonia.Controls.Presenters;
@@ -74,6 +75,12 @@ namespace Avalonia.Controls.Mixins
                             null,
                             presenter.GetValue(ContentPresenter.ChildProperty));
 
+                        if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription))
+                        {
+                            subscription = new CompositeDisposable(previousSubscription, subscription);
+                            subscriptions.Value.Remove(sender);
+                        }
+
                         subscriptions.Value.Add(sender, subscription);
                     }
                 }

+ 6 - 2
src/Avalonia.Interactivity/RoutedEvent.cs

@@ -72,7 +72,9 @@ namespace Avalonia.Interactivity
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            return new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner));
+            var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner));
+            RoutedEventRegistry.Instance.Register(typeof(TOwner), routedEvent);
+            return routedEvent;
         }
 
         public static RoutedEvent<TEventArgs> Register<TEventArgs>(
@@ -83,7 +85,9 @@ namespace Avalonia.Interactivity
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            return new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType);
+            var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType);
+            RoutedEventRegistry.Instance.Register(ownerType, routedEvent);
+            return routedEvent;
         }
 
         public IDisposable AddClassHandler(

+ 90 - 0
src/Avalonia.Interactivity/RoutedEventRegistry.cs

@@ -0,0 +1,90 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Interactivity
+{
+    /// <summary>
+    /// Tracks registered <see cref="RoutedEvent"/>s.
+    /// </summary>
+    public class RoutedEventRegistry
+    {
+        private readonly Dictionary<Type, List<RoutedEvent>> _registeredRoutedEvents =
+            new Dictionary<Type, List<RoutedEvent>>();
+
+        /// <summary>
+        /// Gets the <see cref="RoutedEventRegistry"/> instance.
+        /// </summary>
+        public static RoutedEventRegistry Instance { get; }
+            = new RoutedEventRegistry();
+
+        /// <summary>
+        /// Registers a <see cref="RoutedEvent"/> on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="event">The event.</param>
+        /// <remarks>
+        /// You won't usually want to call this method directly, instead use the
+        /// <see cref="RoutedEvent.Register{TOwner, TEventArgs}(string, RoutingStrategies)"/>
+        /// method.
+        /// </remarks>
+        public void Register(Type type, RoutedEvent @event)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(@event != null);
+
+            if (!_registeredRoutedEvents.TryGetValue(type, out var list))
+            {
+                list = new List<RoutedEvent>();
+                _registeredRoutedEvents.Add(type, list);
+            }
+            list.Add(@event);
+        }
+
+        /// <summary>
+        /// Returns all routed events, that are currently registered in the event registry.
+        /// </summary>
+        /// <returns>All routed events, that are currently registered in the event registry.</returns>
+        public IEnumerable<RoutedEvent> GetAllRegistered()
+        {
+            foreach (var events in _registeredRoutedEvents.Values)
+            {
+                foreach (var e in events)
+                {
+                    yield return e;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns all routed events registered with the provided type.
+        /// If the type is not found or does not provide any routed events, an empty list is returned.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>All routed events registered with the provided type.</returns>
+        public IReadOnlyList<RoutedEvent> GetRegistered(Type type)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            if (_registeredRoutedEvents.TryGetValue(type, out var events))
+            {
+                return events;
+            }
+
+            return Array.Empty<RoutedEvent>();
+        }
+
+        /// <summary>
+        /// Returns all routed events registered with the provided type.         
+        /// If the type is not found or does not provide any routed events, an empty list is returned.
+        /// </summary>
+        /// <typeparam name="TOwner">The type.</typeparam>
+        /// <returns>All routed events registered with the provided type.</returns>
+        public IReadOnlyList<RoutedEvent> GetRegistered<TOwner>()
+        {
+            return GetRegistered(typeof(TOwner));
+        }
+    }
+}

+ 106 - 0
tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs

@@ -0,0 +1,106 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Collections;
+using Avalonia.Controls.Mixins;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Mixins
+{
+    public class ContentControlMixinTests
+    {
+        [Fact]
+        public void Multiple_Mixin_Usages_Should_Not_Throw()
+        {
+            var target = new TestControl()
+            {
+                Template = new FuncControlTemplate(_ => new Panel
+                {
+                    Children =
+                    {
+                        new ContentPresenter { Name = "Content_1_Presenter" },
+                        new ContentPresenter { Name = "Content_2_Presenter" }
+                    }
+                })
+            };
+
+            var ex = Record.Exception(() => target.ApplyTemplate());
+
+            Assert.Null(ex);
+        }
+
+        [Fact]
+        public void Replacing_Template_Releases_Events()
+        {
+            var p1 = new ContentPresenter { Name = "Content_1_Presenter" };
+            var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
+            var target = new TestControl
+            {
+                Template = new FuncControlTemplate(_ => new Panel
+                {
+                    Children =
+                    {
+                        p1,
+                        p2
+                    }
+                })
+            };
+            target.ApplyTemplate();
+
+            Control tc;
+
+            p1.Content = tc = new Control();
+            p1.UpdateChild();
+            Assert.Contains(tc, target.GetLogicalChildren());
+
+            p2.Content = tc = new Control();
+            p2.UpdateChild();
+            Assert.Contains(tc, target.GetLogicalChildren());
+
+            target.Template = null;
+
+            p1.Content = tc = new Control();
+            p1.UpdateChild();
+            Assert.DoesNotContain(tc, target.GetLogicalChildren());
+
+            p2.Content = tc = new Control();
+            p2.UpdateChild();
+            Assert.DoesNotContain(tc, target.GetLogicalChildren());
+
+        }
+
+        private class TestControl : TemplatedControl
+        {
+            public static readonly StyledProperty<object> Content1Property =
+                AvaloniaProperty.Register<TestControl, object>(nameof(Content1));
+
+            public static readonly StyledProperty<object> Content2Property =
+                AvaloniaProperty.Register<TestControl, object>(nameof(Content2));
+
+            static TestControl()
+            {
+                ContentControlMixin.Attach<TestControl>(Content1Property, x => x.LogicalChildren, "Content_1_Presenter");
+                ContentControlMixin.Attach<TestControl>(Content2Property, x => x.LogicalChildren, "Content_2_Presenter");
+            }
+
+            public object Content1
+            {
+                get { return GetValue(Content1Property); }
+                set { SetValue(Content1Property, value); }
+            }
+
+            public object Content2
+            {
+                get { return GetValue(Content2Property); }
+                set { SetValue(Content2Property, value); }
+            }
+        }
+    }
+}

+ 49 - 0
tests/Avalonia.Interactivity.UnitTests/RoutedEventRegistryTests.cs

@@ -0,0 +1,49 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Xunit;
+
+namespace Avalonia.Interactivity.UnitTests
+{
+    public class RoutedEventRegistryTests
+    {
+        [Fact]
+        public void Pointer_Events_Should_Be_Registered()
+        {
+            var expectedEvents = new List<RoutedEvent> { InputElement.PointerPressedEvent, InputElement.PointerReleasedEvent }; 
+            var registeredEvents = RoutedEventRegistry.Instance.GetRegistered<InputElement>();
+            Assert.Contains(registeredEvents, expectedEvents.Contains);
+        }
+
+        [Fact]
+        public void ClickEvent_Should_Be_Registered_On_Button()
+        {
+            var expectedEvents = new List<RoutedEvent> { Button.ClickEvent };
+            var registeredEvents = RoutedEventRegistry.Instance.GetRegistered<Button>();
+            Assert.Contains(registeredEvents, expectedEvents.Contains);
+        }
+
+        [Fact]
+        public void ClickEvent_Should_Not_Be_Registered_On_ContentControl()
+        {
+            // force ContentControl type to be loaded
+            new ContentControl();
+            var expectedEvents = new List<RoutedEvent> { Button.ClickEvent };
+            var registeredEvents = RoutedEventRegistry.Instance.GetRegistered<ContentControl>();
+            Assert.DoesNotContain(registeredEvents, expectedEvents.Contains);
+        }
+
+        [Fact]
+        public void InputElement_Events_Should_Not_Be_Registered_On_Button()
+        {
+            // force Button type to be loaded
+            new Button();
+            var expectedEvents = new List<RoutedEvent> { InputElement.PointerPressedEvent, InputElement.PointerReleasedEvent };
+            var registeredEvents = RoutedEventRegistry.Instance.GetRegistered<Button>();
+            Assert.DoesNotContain(registeredEvents, expectedEvents.Contains);
+        }
+    }
+}