Changes auto-generation to be a behavior

This allows us to specify the priority (order) and make sure that the AutoGenerateBehavior only generates signals for properties that don't get any signals by other behaviors
This commit is contained in:
devmil 2020-10-21 21:58:09 +02:00 committed by Paul Knopf
parent 72f9b2fdc7
commit 4e54f975cb
8 changed files with 184 additions and 23 deletions

View file

@ -0,0 +1,68 @@
using FluentAssertions;
using Xunit;
namespace Qml.Net.Tests.Qml
{
public class SomeTestClass
{
public string PropertyWithoutSignal { get; set; }
[NotifySignal] public string PropertyWithNotifySignal { get; set; }
[NotifySignal("myCustomNotifySignal")] public string PropertyWithCustomNotifySignal { get; set; }
public void TriggerDefaultSignal()
{
this.ActivateSignal("dynamic__PropertyWithoutSignalChanged");
}
public bool DefaultSignalReceived { get; set; } = false;
public void TriggerNotifySignal()
{
this.ActivateNotifySignal(nameof(PropertyWithNotifySignal));
}
public bool NotifySignalReceived { get; set; } = false;
public void TriggerCustomNotifySignal()
{
this.ActivateNotifySignal(nameof(PropertyWithCustomNotifySignal));
}
public bool CustomNotifySignalReceived { get; set; } = false;
}
public class AutoSignalTests : BaseQmlTestsWithInstance<SomeTestClass>
{
public AutoSignalTests()
: base(true)
{
}
[Fact]
public void Does_register_autoSignals()
{
RunQmlTest(
"testClass",
@"
testClass.dynamic__PropertyWithoutSignalChanged.connect(function() {
testClass.defaultSignalReceived = true;
})
testClass.propertyWithNotifySignalChanged.connect(function() {
testClass.notifySignalReceived = true;
})
testClass.myCustomNotifySignal.connect(function() {
testClass.customNotifySignalReceived = true;
})
testClass.triggerDefaultSignal();
testClass.triggerNotifySignal();
testClass.triggerCustomNotifySignal();
");
Instance.DefaultSignalReceived.Should().Be(true);
Instance.NotifySignalReceived.Should().Be(true);
Instance.CustomNotifySignalReceived.Should().Be(true);
}
}
}

View file

@ -76,8 +76,9 @@ namespace Qml.Net.Tests.Qml
{
protected readonly Mock<T> Mock;
protected BaseQmlTests()
protected BaseQmlTests(bool enableAutoSignals = false)
{
QmlNetConfig.AutoGenerateNotifySignals = enableAutoSignals;
RegisterType<T>();
Mock = new Mock<T>();
TypeCreator.SetInstance(typeof(T), Mock.Object);
@ -89,8 +90,9 @@ namespace Qml.Net.Tests.Qml
{
protected readonly T Instance;
protected BaseQmlTestsWithInstance()
protected BaseQmlTestsWithInstance(bool enableAutoSignals = false)
{
QmlNetConfig.AutoGenerateNotifySignals = enableAutoSignals;
RegisterType<T>();
Instance = new T();
TypeCreator.SetInstance(typeof(T), Instance);
@ -104,8 +106,8 @@ namespace Qml.Net.Tests.Qml
protected BaseQmlMvvmTestsWithInstance(bool autogenerateSignals = false)
{
QmlNetConfig.AutoGenerateNotifySignals = autogenerateSignals;
InteropBehaviors.ClearQmlInteropBehaviors();
QmlNetConfig.AutoGenerateNotifySignals = autogenerateSignals;
InteropBehaviors.RegisterQmlInteropBehavior(new MvvmQmlInteropBehavior());
RegisterType<T>();

View file

@ -0,0 +1,71 @@
using System;
using Qml.Net.Internal.Types;
namespace Qml.Net.Internal.Behaviors
{
// All properties have a default notify signal.
// This is so that when we read properties in QML,
// we don't get errors with "NON-NOTIFY PROPERTY BOUND
public class AutoGenerateNotifySignalsBehavior : IQmlInteropBehavior
{
public int Priority => 1000;
public bool IsApplicableFor(Type type)
{
return true;
}
void IQmlInteropBehavior.OnNetTypeInfoCreated(NetTypeInfo netTypeInfo, Type forType)
{
if (!IsApplicableFor(forType))
{
return;
}
for (var i = 0; i < netTypeInfo.PropertyCount; i++)
{
int? existingSignalIndex = null;
var property = netTypeInfo.GetProperty(i);
if (property.NotifySignal != null)
{
// In this case some other behavior or the user has already set up a notify signal for this property.
// We don't want to destroy that.
continue;
}
var signalName = $"dynamic__{property.Name}Changed";
// Check if this signal already has been registered.
for (var signalIndex = 0; signalIndex < netTypeInfo.SignalCount; signalIndex++)
{
var signal = netTypeInfo.GetSignal(signalIndex);
if (string.Equals(signalName, signal.Name))
{
existingSignalIndex = signalIndex;
break;
}
}
if (existingSignalIndex.HasValue)
{
// Signal for this property is already existent but not registered (we check that above).
property.NotifySignal = netTypeInfo.GetSignal(existingSignalIndex.Value);
continue;
}
// Create a new signal and link it to the property.
var notifySignalInfo = new NetSignalInfo(netTypeInfo, signalName);
netTypeInfo.AddSignal(notifySignalInfo);
property.NotifySignal = notifySignalInfo;
}
}
public void OnObjectEntersNative(object instance, ulong objectId)
{
// NOOP
}
public void OnObjectLeavesNative(object instance, ulong objectId)
{
// NOOP
}
}
}

View file

@ -18,7 +18,7 @@ namespace Qml.Net.Internal.Behaviors
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string Name { get; }
public string SignalName { get; }
}
@ -42,6 +42,8 @@ namespace Qml.Net.Internal.Behaviors
private static readonly Dictionary<Type, MvvmTypeInfo> TypeInfos = new Dictionary<Type, MvvmTypeInfo>();
public int Priority => 1;
public bool IsApplicableFor(Type type)
{
return typeof(INotifyPropertyChanged).IsAssignableFrom(type);
@ -82,7 +84,7 @@ namespace Qml.Net.Internal.Behaviors
{
return;
}
// Fire signal according to the property that got changed.
var type = sender.GetType();
if (TypeInfos.TryGetValue(type, out var typeInfo))
@ -127,7 +129,7 @@ namespace Qml.Net.Internal.Behaviors
}
var signalName = CalculateSignalNameFromPropertyName(property.Name);
mvvmTypeInfo.AddPropertyInfo(property.Name, signalName);
// Check if this signal already has been registered.
for (var signalIndex = 0; signalIndex < netTypeInfo.SignalCount; signalIndex++)
{
@ -144,7 +146,7 @@ namespace Qml.Net.Internal.Behaviors
property.NotifySignal = netTypeInfo.GetSignal(existingSignalIndex.Value);
continue;
}
// Create a new signal and link it to the property.
var notifySignalInfo = new NetSignalInfo(netTypeInfo, signalName);
netTypeInfo.AddSignal(notifySignalInfo);

View file

@ -147,18 +147,6 @@ namespace Qml.Net.Internal
NetSignalInfo notifySignal = null;
var notifySignalAttribute = propertyInfo.GetCustomAttribute<NotifySignalAttribute>();
// All properties have a default notify signal.
// This is so that when we read properties in QML,
// we don't get errors with "NON-NOTIFY PROPERTY BOUND
if (notifySignalAttribute == null && QmlNetConfig.AutoGenerateNotifySignals)
{
var dynamicName = $"dynamic__{propertyInfo.Name}Changed";
notifySignalAttribute = new NotifySignalAttribute
{
Name = dynamicName
};
}
if (notifySignalAttribute != null)
{
var name = notifySignalAttribute.Name;

View file

@ -5,6 +5,11 @@ namespace Qml.Net.Internal
{
internal interface IQmlInteropBehavior
{
/// <summary>
/// Gets the priority of this behavior (lower is more prior)
/// </summary>
int Priority { get; }
bool IsApplicableFor(Type type);
void OnNetTypeInfoCreated(NetTypeInfo netTypeInfo, Type forType);

View file

@ -8,13 +8,14 @@ namespace Qml.Net.Internal
internal static class InteropBehaviors
{
private static List<IQmlInteropBehavior> _QmlInteropBehaviors = new List<IQmlInteropBehavior>();
public static IEnumerable<IQmlInteropBehavior> QmlInteropBehaviors => _QmlInteropBehaviors;
private static IEnumerable<IQmlInteropBehavior> GetApplicableInteropBehaviors(Type forType)
{
return _QmlInteropBehaviors
.Where(b => b.IsApplicableFor(forType));
.Where(b => b.IsApplicableFor(forType))
.OrderBy(b => b.Priority);
}
/// <summary>
@ -33,6 +34,11 @@ namespace Qml.Net.Internal
}
}
public static void RemoveQmlInteropBehavior<TBehavior>()
{
_QmlInteropBehaviors.RemoveAll(b => typeof(TBehavior).IsAssignableFrom(b.GetType()));
}
public static void ClearQmlInteropBehaviors()
{
_QmlInteropBehaviors.Clear();

View file

@ -1,4 +1,6 @@
using System;
using Qml.Net.Internal;
using Qml.Net.Internal.Behaviors;
namespace Qml.Net
{
@ -18,8 +20,23 @@ namespace Qml.Net
public static bool ShouldEnsureUIThread { get; set; } = true;
public static bool AutoGenerateNotifySignals { get; set; } = false;
public static bool AutoGenerateNotifySignals
{
get => _autoGenerateNotifySignals;
set
{
_autoGenerateNotifySignals = value;
if (value)
{
InteropBehaviors.RegisterQmlInteropBehavior(new AutoGenerateNotifySignalsBehavior());
}
else
{
InteropBehaviors.RemoveQmlInteropBehavior<AutoGenerateNotifySignalsBehavior>();
}
}
}
public static Action EnsureUIThreadDelegate = () =>
{
if (!QCoreApplication.IsMainThread)
@ -33,6 +50,8 @@ Stack Trace:
}
};
private static bool _autoGenerateNotifySignals = false;
internal static void EnsureUIThread()
{
if (!ShouldEnsureUIThread)