mirror of
https://github.com/qmlnet/qmlnet.git
synced 2026-05-15 14:15:54 -06:00
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:
parent
72f9b2fdc7
commit
4e54f975cb
8 changed files with 184 additions and 23 deletions
68
src/net/Qml.Net.Tests/Qml/AutoSignalTests.cs
Normal file
68
src/net/Qml.Net.Tests/Qml/AutoSignalTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue