1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Dynamic data model - Removed DataModel constraint

Dynamic data model - Added ability to provide custom description attribute
This commit is contained in:
Robert 2021-04-18 22:22:16 +02:00
parent 5e345fed7c
commit 8cba36f5d4
4 changed files with 182 additions and 96 deletions

View File

@ -4,20 +4,20 @@ using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
/// <summary>
/// Provides data about dynamic data model related events
/// Provides data about dynamic data model child related events
/// </summary>
public class DynamicDataModelEventArgs : EventArgs
public class DynamicDataModelChildEventArgs : EventArgs
{
internal DynamicDataModelEventArgs(DataModel dynamicDataModel, string key)
internal DynamicDataModelChildEventArgs(object? dynamicChild, string key)
{
DynamicDataModel = dynamicDataModel;
DynamicChild = dynamicChild;
Key = key;
}
/// <summary>
/// Gets the dynamic data model
/// Gets the dynamic data model child
/// </summary>
public DataModel DynamicDataModel { get; }
public object? DynamicChild { get; }
/// <summary>
/// Gets the key of the dynamic data model on the parent <see cref="DataModel" />

View File

@ -15,6 +15,8 @@ namespace Artemis.Core
{
private Expression<Func<object, object>>? _accessorLambda;
private DataModel? _dynamicDataModel;
private Type _dynamicDataModelType;
private DataModelPropertyAttribute _dynamicDataModelAttribute;
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
{
@ -49,12 +51,6 @@ namespace Artemis.Core
/// </summary>
public DataModelPathSegmentType Type { get; private set; }
/// <summary>
/// Gets the type of dynamic data model this path points to
/// <para>Not used if the <see cref="Type" /> is <see cref="DataModelPathSegmentType.Static" /></para>
/// </summary>
public Type? DynamicDataModelType { get; private set; }
/// <summary>
/// Gets the previous segment in the path
/// </summary>
@ -114,7 +110,7 @@ namespace Artemis.Core
{
// Dynamic types have a data model description
if (Type == DataModelPathSegmentType.Dynamic)
return (GetValue() as DataModel)?.DataModelDescription;
return _dynamicDataModelAttribute;
if (IsStartSegment && DataModelPath.Target != null)
return DataModelPath.Target.DataModelDescription;
if (IsStartSegment)
@ -187,12 +183,12 @@ namespace Artemis.Core
return CreateExpression(parameter, expression, nullCondition);
// If a dynamic data model is found the use that
bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel? dynamicDataModel);
if (hasDynamicDataModel && dynamicDataModel != null)
DetermineDynamicType(dynamicDataModel);
bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild);
if (hasDynamicChild && dynamicChild?.Value != null)
DetermineDynamicType(dynamicChild.Value, dynamicChild.Attribute);
_dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded;
_dynamicDataModel.DynamicDataModelRemoved += DynamicDataModelOnDynamicDataModelRemoved;
_dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded;
_dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved;
}
return CreateExpression(parameter, expression, nullCondition);
@ -219,7 +215,7 @@ namespace Artemis.Core
accessorExpression = Expression.Call(
expression,
nameof(DataModel.DynamicChild),
DynamicDataModelType != null ? new[] {DynamicDataModelType} : null,
_dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null,
Expression.Constant(Identifier)
);
@ -236,10 +232,11 @@ namespace Artemis.Core
return accessorExpression;
}
private void DetermineDynamicType(DataModel dynamicDataModel)
private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute)
{
Type = DataModelPathSegmentType.Dynamic;
DynamicDataModelType = dynamicDataModel.GetType();
_dynamicDataModelType = dynamicDataModel.GetType();
_dynamicDataModelAttribute = attribute;
}
private void DetermineStaticType(Type previousType)
@ -263,8 +260,8 @@ namespace Artemis.Core
{
if (_dynamicDataModel != null)
{
_dynamicDataModel.DynamicDataModelAdded -= DynamicDataModelOnDynamicDataModelAdded;
_dynamicDataModel.DynamicDataModelRemoved -= DynamicDataModelOnDynamicDataModelRemoved;
_dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded;
_dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved;
}
Type = DataModelPathSegmentType.Invalid;
@ -285,15 +282,15 @@ namespace Artemis.Core
#region Event handlers
private void DynamicDataModelOnDynamicDataModelAdded(object? sender, DynamicDataModelEventArgs e)
private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e)
{
if (e.Key == Identifier)
DataModelPath.Initialize();
}
private void DynamicDataModelOnDynamicDataModelRemoved(object? sender, DynamicDataModelEventArgs e)
private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
{
if (e.DynamicDataModel == _dynamicDataModel)
if (e.DynamicChild == _dynamicDataModel)
DataModelPath.Initialize();
}

View File

@ -14,7 +14,7 @@ namespace Artemis.Core.DataModelExpansions
/// </summary>
public abstract class DataModel
{
private readonly Dictionary<string, DataModel> _dynamicDataModels = new();
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
/// <summary>
/// Creates a new instance of the <see cref="DataModel" /> class
@ -47,10 +47,10 @@ namespace Artemis.Core.DataModelExpansions
public bool IsExpansion { get; internal set; }
/// <summary>
/// Gets an read-only dictionary of all dynamic data models
/// Gets an read-only dictionary of all dynamic children
/// </summary>
[DataModelIgnore]
public ReadOnlyDictionary<string, DataModel> DynamicDataModels => new(_dynamicDataModels);
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren);
/// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored
@ -67,124 +67,211 @@ namespace Artemis.Core.DataModelExpansions
}
/// <summary>
/// Adds a dynamic data model to this data model
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="dynamicDataModel">The dynamic data model to add</param>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="name">An optional name, if not provided the key will be used in a humanized form</param>
/// <param name="description">An optional description</param>
public T AddDynamicChild<T>(T dynamicDataModel, string key, string? name = null, string? description = null) where T : DataModel
/// <param name="name">An optional human readable name, if not provided the key will be used in a humanized form</param>
public T AddDynamicChild<T>(T dynamicChild, string key, string? name = null)
{
if (dynamicDataModel == null)
throw new ArgumentNullException(nameof(dynamicDataModel));
if (dynamicChild == null)
throw new ArgumentNullException(nameof(dynamicChild));
if (key == null)
throw new ArgumentNullException(nameof(key));
if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicDataModels.ContainsKey(key))
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
if (_dynamicDataModels.ContainsValue(dynamicDataModel))
if (_dynamicChildren.ContainsKey(key))
{
string existingKey = _dynamicDataModels.First(kvp => kvp.Value == dynamicDataModel).Key;
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
$"because the dynamic data model is already added with key '{existingKey}.");
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
}
if (GetType().GetProperty(key) != null)
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use by a static property on this data model.");
}
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute
DataModelPropertyAttribute attribute = new()
{
Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name
};
if (dynamicChild is DataModel dynamicDataModel)
{
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = attribute;
}
_dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild));
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="name">A human readable for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
public T AddDynamicChild<T>(T dynamicChild, string key, string name, string description)
{
if (dynamicChild == null)
throw new ArgumentNullException(nameof(dynamicChild));
if (key == null)
throw new ArgumentNullException(nameof(key));
if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicChildren.ContainsKey(key))
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
}
if (GetType().GetProperty(key) != null)
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use by a static property on this data model.");
}
DataModelPropertyAttribute attribute = new()
{
Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name,
Description = description
};
_dynamicDataModels.Add(key, dynamicDataModel);
if (dynamicChild is DataModel dynamicDataModel)
{
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = attribute;
}
OnDynamicDataModelAdded(new DynamicDataModelEventArgs(dynamicDataModel, key));
return dynamicDataModel;
_dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild));
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Removes a dynamic data model from the data model by its key
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the dynamic data model to remove</param>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
public T AddDynamicChild<T>(T dynamicChild, string key, DataModelPropertyAttribute attribute)
{
if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
if (key == null) throw new ArgumentNullException(nameof(key));
if (attribute == null) throw new ArgumentNullException(nameof(attribute));
if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicChildren.ContainsKey(key))
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
}
if (GetType().GetProperty(key) != null)
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use by a static property on this data model.");
}
// Make sure a name is on the attribute or funny things might happen
attribute.Name ??= key.Humanize();
if (dynamicChild is DataModel dynamicDataModel)
{
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = attribute;
}
_dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild));
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
public void RemoveDynamicChildByKey(string key)
{
_dynamicDataModels.TryGetValue(key, out DataModel? childDataModel);
if (childDataModel == null)
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return;
_dynamicDataModels.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(childDataModel, key));
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild.Value, key));
}
/// <summary>
/// Removes a dynamic data model from this data model
/// Removes a dynamic child from this data model
/// </summary>
/// <param name="dynamicDataModel">The dynamic data model to remove</param>
public void RemoveDynamicChild(DataModel dynamicDataModel)
/// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild<T>(T dynamicChild) where T : class
{
List<string> keys = _dynamicDataModels.Where(kvp => kvp.Value == dynamicDataModel).Select(kvp => kvp.Key).ToList();
List<string> keys = _dynamicChildren.Where(kvp => kvp.Value.Value == dynamicChild).Select(kvp => kvp.Key).ToList();
foreach (string key in keys)
{
_dynamicDataModels.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(dynamicDataModel, key));
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
}
}
/// <summary>
/// Removes all dynamic data models from this data model
/// Removes all dynamic children from this data model
/// </summary>
public void ClearDynamicChildren()
{
while (_dynamicDataModels.Any())
RemoveDynamicChildByKey(_dynamicDataModels.First().Key);
while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
}
/// <summary>
/// Gets a dynamic data model of type <typeparamref name="T" /> by its key
/// Gets a dynamic child of type <typeparamref name="T" /> by its key
/// </summary>
/// <typeparam name="T">The type of data model you expect</typeparam>
/// <param name="key">The unique key of the dynamic data model</param>
/// <returns>If found, the dynamic data model otherwise <c>null</c></returns>
public T? DynamicChild<T>(string key) where T : DataModel
/// <param name="key">The unique key of the dynamic child</param>
/// <returns>If found, the dynamic child otherwise <c>null</c></returns>
public T? DynamicChild<T>(string key)
{
_dynamicDataModels.TryGetValue(key, out DataModel? value);
return value as T;
}
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return default;
#region Events
/// <summary>
/// Occurs when a dynamic data model has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelAdded;
/// <summary>
/// Occurs when a dynamic data model has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelRemoved;
/// <summary>
/// Invokes the <see cref="DynamicDataModelAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e)
{
DynamicDataModelAdded?.Invoke(this, e);
if (dynamicChild.Value is not T)
return default;
return (T?) dynamicChild.Value;
}
/// <summary>
/// Invokes the <see cref="DynamicDataModelRemoved" /> event
/// Occurs when a dynamic child has been added to this data model
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e)
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{
DynamicDataModelRemoved?.Invoke(this, e);
DynamicChildAdded?.Invoke(this, e);
}
#endregion
/// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
}
}
/// <summary>
/// Represents a record of a dynamic child value with its property attribute
/// </summary>
public record DynamicChild(DataModelPropertyAttribute Attribute, object? Value);
}

View File

@ -239,9 +239,10 @@ namespace Artemis.UI.Shared
// Add missing dynamic children
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
if (value is DataModel dataModel)
foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels)
{
foreach (var (key, dynamicChild) in dataModel.DynamicChildren)
{
string childPath = AppendToPath(kvp.Key);
string childPath = AppendToPath(key);
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue;
@ -249,6 +250,7 @@ namespace Artemis.UI.Shared
if (child != null)
Children.Add(child);
}
}
// Remove dynamic children that have been removed from the data model
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();