diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index 039087fff..7878bc5a6 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -88,11 +88,33 @@ namespace Artemis.Core } /// - /// Returns the default value of the given type + /// Returns the default value of the given type /// public static object GetDefault(this Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } + + /// + /// Determines whether the given type is a generic enumerable + /// + public static bool IsGenericEnumerable(this Type type) + { + return type.GetInterfaces().Any(x => + x.IsGenericType && + x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + } + + /// + /// Determines the type of the provided generic enumerable type + /// + public static Type? GetGenericEnumerableType(this Type type) + { + Type enumerableType = type.GetInterfaces().FirstOrDefault(x => + x.IsGenericType && + x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + + return enumerableType?.GenericTypeArguments[0]; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs index 55da596fa..a3459f385 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs @@ -93,7 +93,7 @@ namespace Artemis.Core if (ListPath != null) { Type listType = ListPath.GetPropertyType()!; - ListType = listType.GetGenericArguments()[0]; + ListType = listType.GetGenericEnumerableType(); IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string); // Create a new root group @@ -129,10 +129,10 @@ namespace Artemis.Core if (!Children.Any()) return false; - if (!(target is IList list)) + if (!(target is IEnumerable enumerable)) return false; - IEnumerable objectList = list.Cast(); + IEnumerable objectList = enumerable.Cast(); return ListOperator switch { ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)), @@ -180,14 +180,14 @@ namespace Artemis.Core DataModelPath listPath = new DataModelPath(null, Entity.ListPath); Type listType = listPath.GetPropertyType()!; // Can't check this on an invalid list, if it becomes valid later lets hope for the best - if (listPath.IsValid && !typeof(IList).IsAssignableFrom(listType)) + if (listPath.IsValid && !listPath.PointsToList) return; ListPath = listPath; SubscribeToListPath(); if (ListPath.IsValid) { - ListType = listType.GetGenericArguments()[0]; + ListType = listType.GetGenericEnumerableType(); IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string); } else diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 1126b8efd..64f62ed68 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -114,8 +114,14 @@ namespace Artemis.Core /// /// Gets a boolean indicating whether this data model path points to a list /// - public bool PointsToList => Segments.LastOrDefault()?.GetPropertyType() != null && - typeof(IList).IsAssignableFrom(Segments.LastOrDefault()?.GetPropertyType()); + public bool PointsToList + { + get + { + Type? type = GetPropertyType(); + return type?.IsGenericEnumerable() ?? false; + } + } internal DataModelPathEntity Entity { get; } diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index bd33eccc2..b6d68045d 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -142,79 +142,6 @@ namespace Artemis.Core.DataModelExpansions return value as T; } - internal bool ContainsPath(string? path) - { - if (path == null) - return false; - string[] parts = path.Split('.'); - Type? current = GetType(); - foreach (string part in parts) - { - PropertyInfo? property = current?.GetProperty(part); - current = property?.PropertyType; - if (property == null) - return false; - } - - return true; - } - - internal Type GetTypeAtPath(string path) - { - if (!ContainsPath(path)) - return null; - - string[] parts = path.Split('.'); - Type current = GetType(); - - Type result = null; - foreach (string part in parts) - { - PropertyInfo? property = current.GetProperty(part); - current = property.PropertyType; - result = property.PropertyType; - } - - return result; - } - - internal Type GetListTypeInPath(string path) - { - if (!ContainsPath(path)) - return null; - - string[] parts = path.Split('.'); - Type current = GetType(); - - int index = 0; - foreach (string part in parts) - { - // Only return a type if the path CONTAINS a list, not if it points TO a list - if (index == parts.Length - 1) - return null; - - PropertyInfo? property = current.GetProperty(part); - - // For lists, look into the list type instead of the list itself - if (typeof(IList).IsAssignableFrom(property.PropertyType)) - return property.PropertyType.GetGenericArguments()[0]; - - current = property.PropertyType; - index++; - } - - return null; - } - - internal Type GetListTypeAtPath(string path) - { - if (!ContainsPath(path)) - return null; - - Type child = GetTypeAtPath(path); - return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null; - } - #region Events /// diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index c0eebd90b..4190752ea 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -9,31 +9,38 @@ namespace Artemis.UI.Shared { public class DataModelListViewModel : DataModelVisualizationViewModel { - private string _count; - private IList _list; - + private string _countDisplay; + private IEnumerable _list; + private int _listCount; + internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { ListChildren = new BindableCollection(); } - public IList List + public IEnumerable List { get => _list; set => SetAndNotify(ref _list, value); } - public BindableCollection ListChildren { get; set; } - - public string Count + public int ListCount { - get => _count; - set => SetAndNotify(ref _count, value); + get => _listCount; + set => SetAndNotify(ref _listCount, value); } + public string CountDisplay + { + get => _countDisplay; + set => SetAndNotify(ref _countDisplay, value); + } + + public BindableCollection ListChildren { get; set; } + public DataModelPropertiesViewModel GetListTypeViewModel(IDataModelUIService dataModelUIService) { - Type listType = DataModelPath.GetPropertyType()?.GenericTypeArguments[0]; + Type listType = DataModelPath.GetPropertyType()?.GetGenericEnumerableType(); if (listType == null) return null; @@ -42,10 +49,8 @@ namespace Artemis.UI.Shared viewModel.Update(dataModelUIService); // Put an empty value into the list type property view model - if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel) - { - return dataModelListClassViewModel; - } + if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel) return dataModelListClassViewModel; + if (viewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel) { dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType); @@ -62,7 +67,7 @@ namespace Artemis.UI.Shared if (Parent != null && !Parent.IsVisualizationExpanded) return; - List = GetCurrentValue() as IList; + List = GetCurrentValue() as IEnumerable; if (List == null) return; @@ -71,7 +76,7 @@ namespace Artemis.UI.Shared { if (item == null) continue; - + DataModelVisualizationViewModel child; if (ListChildren.Count <= index) { @@ -79,7 +84,9 @@ namespace Artemis.UI.Shared ListChildren.Add(child); } else + { child = ListChildren[index]; + } if (child is DataModelListPropertiesViewModel dataModelListClassViewModel) { @@ -96,10 +103,18 @@ namespace Artemis.UI.Shared index++; } - while (ListChildren.Count > List.Count) + ListCount = index + 1; + + while (ListChildren.Count > ListCount) ListChildren.RemoveAt(ListChildren.Count - 1); - Count = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}"; + CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}"; + } + + /// + public override string ToString() + { + return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)"; } protected DataModelVisualizationViewModel CreateListChild(IDataModelUIService dataModelUIService, Type listType) @@ -117,11 +132,5 @@ namespace Artemis.UI.Shared return null; } - - /// - public override string ToString() - { - return $"[List] {DisplayPath ?? Path} - {List?.Count ?? 0} item(s)"; - } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 9313c0111..8fee846e5 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -133,7 +133,9 @@ namespace Artemis.UI.Shared } if (looseMatch) - IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) || t == typeof(Enum) && type.IsEnum); + IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) || + t == typeof(Enum) && type.IsEnum || + t == typeof(IEnumerable<>) && type.IsGenericEnumerable()); else IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum); } @@ -260,12 +262,12 @@ namespace Artemis.UI.Shared // For primitives, create a property view model, it may be null that is fine if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string)) return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth}; - if (typeof(IList).IsAssignableFrom(propertyType)) + if (propertyType.IsGenericEnumerable()) return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth}; // For other value types create a child view model if (propertyType.IsClass || propertyType.IsStruct()) return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth}; - + return null; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs index 5b191e8f8..29fb3a4a6 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs @@ -86,7 +86,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public void Initialize() { TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); - TargetSelectionViewModel.FilterTypes = new[] {typeof(IList)}; + TargetSelectionViewModel.FilterTypes = new[] {typeof(IEnumerable<>)}; TargetSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188)); TargetSelectionViewModel.Placeholder = "Select a list"; TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs index 15ae774a8..9129116e8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Windows; using System.Windows.Media; using Artemis.Core; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Services; using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Shared; @@ -83,19 +82,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public DelegateCommand SelectOperatorCommand { get; } - public void Dispose() - { - if (LeftSideSelectionViewModel != null) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel = null; - } - - DisposeRightSideStatic(); - DisposeRightSideDynamic(); - } - public override void Delete() { base.Delete(); @@ -118,7 +104,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions { LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath); - + Type leftSideType = LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); // Get the supported operators @@ -239,5 +225,18 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions RightSideSelectionViewModel = null; } } + + public void Dispose() + { + if (LeftSideSelectionViewModel != null) + { + LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; + LeftSideSelectionViewModel.Dispose(); + LeftSideSelectionViewModel = null; + } + + DisposeRightSideStatic(); + DisposeRightSideDynamic(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index f7051ca0f..e0f56aa7b 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -69,12 +69,10 @@ - - [List] - +