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

Display conditions - Implemented lists in the core, UI needs more work

This commit is contained in:
Robert 2020-08-12 23:32:30 +02:00
parent ae708fa26a
commit f2f77da953
13 changed files with 288 additions and 84 deletions

View File

@ -35,8 +35,19 @@ namespace Artemis.Core.Models.Profile.Conditions.Abstract
}
}
/// <summary>
/// Evaluates the condition part on the data model
/// </summary>
/// <returns></returns>
public abstract bool Evaluate();
/// <summary>
/// Evaluates the condition part on the given target (currently only for lists)
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
public abstract bool EvaluateObject(object target);
internal abstract void Initialize(IDataModelService dataModelService);
internal abstract void ApplyToEntity();
internal abstract DisplayConditionPartEntity GetEntity();

View File

@ -37,9 +37,12 @@ namespace Artemis.Core.Models.Profile.Conditions
public override bool Evaluate()
{
// If there are less than two children, ignore the boolean operator
if (Children.Count <= 2)
return Children.All(c => c.Evaluate());
// Empty groups are always true
if (Children.Count == 0)
return true;
// Groups with only one child ignore the boolean operator
if (Children.Count == 1)
return Children[0].Evaluate();
switch (BooleanOperator)
{
@ -56,6 +59,25 @@ namespace Artemis.Core.Models.Profile.Conditions
}
}
public override bool EvaluateObject(object target)
{
// Empty groups are always true
if (Children.Count == 0)
return true;
// Groups with only one child ignore the boolean operator
if (Children.Count == 1)
return Children[0].EvaluateObject(target);
return BooleanOperator switch
{
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void ApplyToEntity()
{
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;

View File

@ -1,4 +1,7 @@
using System.Linq;
using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile.Conditions.Abstract;
using Artemis.Core.Plugins.Abstract.DataModels;
@ -44,7 +47,23 @@ namespace Artemis.Core.Models.Profile.Conditions
public override bool Evaluate()
{
return true;
return EvaluateObject(CompiledListAccessor(ListDataModel));
}
public override bool EvaluateObject(object target)
{
if (!(target is IList list))
return false;
var objectList = list.Cast<object>();
return ListOperator switch
{
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
ListOperator.Count => false,
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void ApplyToEntity()
@ -94,11 +113,26 @@ namespace Artemis.Core.Models.Profile.Conditions
{
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
if (dataModel.GetListTypeAtPath(path) == null)
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
}
ListDataModel = dataModel;
ListPropertyPath = path;
if (dataModel != null)
{
var parameter = Expression.Parameter(typeof(object), "listDataModel");
var accessor = path.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, dataModel.GetType()),
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
CompiledListAccessor = lambda.Compile();
}
}
public Func<object, IList> CompiledListAccessor { get; set; }
}
public enum ListOperator

View File

@ -39,10 +39,9 @@ namespace Artemis.Core.Models.Profile.Conditions
public string RightPropertyPath { get; private set; }
public object RightStaticValue { get; private set; }
public Expression<Func<DataModel, DataModel, bool>> DynamicConditionLambda { get; private set; }
public Func<DataModel, DataModel, bool> CompiledDynamicConditionLambda { get; private set; }
public Expression<Func<DataModel, bool>> StaticConditionLambda { get; private set; }
public Func<DataModel, bool> CompiledStaticConditionLambda { get; private set; }
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
public Func<object, bool> CompiledListPredicate { get; private set; }
public void UpdateLeftSide(DataModel dataModel, string path)
{
@ -120,10 +119,9 @@ namespace Artemis.Core.Models.Profile.Conditions
private void CreateExpression()
{
DynamicConditionLambda = null;
CompiledDynamicConditionLambda = null;
StaticConditionLambda = null;
CompiledStaticConditionLambda = null;
CompiledDynamicPredicate = null;
CompiledStaticPredicate = null;
CompiledListPredicate = null;
if (Operator == null)
return;
@ -137,7 +135,7 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override void ApplyToEntity()
{
DisplayConditionPredicateEntity.PredicateType = (int)PredicateType;
DisplayConditionPredicateEntity.PredicateType = (int) PredicateType;
DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath;
@ -151,10 +149,18 @@ namespace Artemis.Core.Models.Profile.Conditions
public override bool Evaluate()
{
if (CompiledDynamicConditionLambda != null)
return CompiledDynamicConditionLambda(LeftDataModel, RightDataModel);
if (CompiledStaticConditionLambda != null)
return CompiledStaticConditionLambda(LeftDataModel);
if (CompiledDynamicPredicate != null)
return CompiledDynamicPredicate(LeftDataModel, RightDataModel);
if (CompiledStaticPredicate != null)
return CompiledStaticPredicate(LeftDataModel);
return false;
}
public override bool EvaluateObject(object target)
{
if (CompiledListPredicate != null)
return CompiledListPredicate(target);
return false;
}
@ -194,7 +200,7 @@ namespace Artemis.Core.Models.Profile.Conditions
// Use the left side type so JSON.NET has a better idea what to do
var leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
object rightSideValue;
try
{
rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType);
@ -286,26 +292,42 @@ namespace Artemis.Core.Models.Profile.Conditions
if (LeftDataModel == null || RightDataModel == null || Operator == null)
return;
var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel");
var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(leftSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
var rightSideParameter = Expression.Parameter(typeof(DataModel), "rightDataModel");
var rightSideAccessor = RightPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(rightSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
Expression leftSideAccessor;
Expression rightSideAccessor;
ParameterExpression leftSideParameter;
ParameterExpression rightSideParameter = null;
if (isListExpression)
{
// List accessors share the same parameter because a list always contains one item per entry
leftSideParameter = Expression.Parameter(typeof(object), "listItem");
leftSideAccessor = CreateListAccessor(LeftDataModel, LeftPropertyPath, leftSideParameter);
rightSideAccessor = CreateListAccessor(RightDataModel, RightPropertyPath, leftSideParameter);
}
else
{
leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out leftSideParameter);
rightSideAccessor = CreateAccessor(RightDataModel, RightPropertyPath, "right", out rightSideParameter);
}
// A conversion may be required if the types differ
// This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here
if (rightSideAccessor.Type != leftSideAccessor.Type)
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
var dynamicConditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
DynamicConditionLambda = Expression.Lambda<Func<DataModel, DataModel, bool>>(dynamicConditionExpression, leftSideParameter, rightSideParameter);
CompiledDynamicConditionLambda = DynamicConditionLambda.Compile();
if (isListExpression)
{
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
CompiledListPredicate = lambda.Compile();
}
else
{
var lambda = Expression.Lambda<Func<DataModel, DataModel, bool>>(conditionExpression, leftSideParameter, rightSideParameter);
CompiledDynamicPredicate = lambda.Compile();
}
}
private void CreateStaticExpression()
@ -313,11 +335,18 @@ namespace Artemis.Core.Models.Profile.Conditions
if (LeftDataModel == null || Operator == null)
return;
var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel");
var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(leftSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
Expression leftSideAccessor;
ParameterExpression leftSideParameter;
if (isListExpression)
{
// List accessors share the same parameter because a list always contains one item per entry
leftSideParameter = Expression.Parameter(typeof(object), "listItem");
leftSideAccessor = CreateListAccessor(LeftDataModel, LeftPropertyPath, leftSideParameter);
}
else
leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out leftSideParameter);
// If the left side is a value type but the input is empty, this isn't a valid expression
if (leftSideAccessor.Type.IsValueType && RightStaticValue == null)
@ -330,8 +359,42 @@ namespace Artemis.Core.Models.Profile.Conditions
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
StaticConditionLambda = Expression.Lambda<Func<DataModel, bool>>(conditionExpression, leftSideParameter);
CompiledStaticConditionLambda = StaticConditionLambda.Compile();
if (isListExpression)
{
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
CompiledListPredicate = lambda.Compile();
}
else
{
var lambda = Expression.Lambda<Func<DataModel, bool>>(conditionExpression, leftSideParameter);
CompiledStaticPredicate = lambda.Compile();
}
}
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
{
var listType = dataModel.GetListTypeAtPath(path);
if (listType != null)
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list");
parameter = Expression.Parameter(typeof(object), parameterName + "DataModel");
return path.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, dataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
}
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
{
var listType = dataModel.GetListTypeAtPath(path);
if (listType == null)
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");
path = dataModel.GetListInnerPath(path);
return path.Split('.').Aggregate<string, Expression>(
Expression.Convert(listParameter, listType), // Cast to the appropriate type
Expression.Property
);
}
public override string ToString()

View File

@ -1,7 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Artemis.Core.Exceptions;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.Core.Plugins.Models;
@ -27,8 +31,14 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
var current = GetType();
foreach (var part in parts)
{
var property = current?.GetProperty(part);
current = property?.PropertyType;
var property = current.GetProperty(part);
// For lists, look into the list type instead of the list itself
if (property != null && typeof(IList).IsAssignableFrom(property.PropertyType))
current = property.PropertyType.GetGenericArguments()[0];
else
current = property?.PropertyType;
if (property == null)
return false;
}
@ -48,13 +58,63 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
foreach (var part in parts)
{
var property = current.GetProperty(part);
current = property.PropertyType;
// For lists, look into the list type instead of the list itself
if (typeof(IList).IsAssignableFrom(property.PropertyType))
current = property.PropertyType.GetGenericArguments()[0];
else
current = property.PropertyType;
result = property.PropertyType;
}
return result;
}
public Type GetListTypeAtPath(string path)
{
if (!ContainsPath(path))
return null;
var parts = path.Split('.');
var current = GetType();
foreach (var part in parts)
{
var 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;
}
return null;
}
public string GetListInnerPath(string path)
{
if (GetListTypeAtPath(path) == null)
throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list");
var parts = path.Split('.');
var current = GetType();
for (var index = 0; index < parts.Length; index++)
{
var part = parts[index];
var property = current.GetProperty(part);
if (typeof(IList).IsAssignableFrom(property.PropertyType))
return string.Join('.', parts.Skip(index + 1).ToList());
current = property.PropertyType;
}
return null;
}
/// <summary>
/// Returns a read-only list of all properties in this datamodel that are to be ignored
/// </summary>

View File

@ -34,6 +34,10 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
set => SetAndNotify(ref _displayValue, value);
}
public override string PropertyPath => Parent?.PropertyPath;
public override string DisplayPropertyPath => Parent?.DisplayPropertyPath;
public override void Update(IDataModelVisualizationService dataModelVisualizationService)
{
// Display value gets updated by parent, don't do anything if it is null

View File

@ -3,7 +3,6 @@ using System.Collections;
using System.Reflection;
using Artemis.Core.Extensions;
using Artemis.Core.Plugins.Abstract.DataModels;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.UI.Shared.Services;
using Stylet;
@ -26,12 +25,6 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
set => SetAndNotify(ref _list, value);
}
public DataModelVisualizationViewModel ListTypePropertyViewModel
{
get => _listTypePropertyViewModel;
set => SetAndNotify(ref _listTypePropertyViewModel, value);
}
public BindableCollection<DataModelVisualizationViewModel> ListChildren { get; set; }
public string Count
@ -40,6 +33,30 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
set => SetAndNotify(ref _count, value);
}
public DataModelPropertiesViewModel GetListTypeViewModel(IDataModelVisualizationService dataModelVisualizationService)
{
// Create a property VM describing the type of the list
var viewModel = CreateListChild(dataModelVisualizationService, List.GetType().GenericTypeArguments[0]);
// Put an empty value into the list type property view model
if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel)
{
dataModelListClassViewModel.DisplayValue = Activator.CreateInstance(dataModelListClassViewModel.ListType);
dataModelListClassViewModel.Update(dataModelVisualizationService);
return dataModelListClassViewModel;
}
if (viewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel)
{
dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType);
var wrapper = new DataModelPropertiesViewModel(null,null,null);
wrapper.Children.Add(dataModelListPropertyViewModel);
return wrapper;
}
return null;
}
public override void Update(IDataModelVisualizationService dataModelVisualizationService)
{
if (Parent != null && !Parent.IsVisualizationExpanded)
@ -49,20 +66,6 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
if (List == null)
return;
if (ListTypePropertyViewModel == null)
{
// Create a property VM describing the type of the list
ListTypePropertyViewModel = CreateListChild(dataModelVisualizationService, List.GetType().GenericTypeArguments[0]);
// Put an empty value into the list type property view model
if (ListTypePropertyViewModel is DataModelListPropertiesViewModel dataModelListClassViewModel)
dataModelListClassViewModel.DisplayValue = Activator.CreateInstance(dataModelListClassViewModel.ListType);
else if (ListTypePropertyViewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel)
dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType);
ListTypePropertyViewModel.Update(dataModelVisualizationService);
}
var index = 0;
foreach (var item in List)
{

View File

@ -1,12 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Windows.Documents;
using Artemis.Core.Extensions;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Abstract.DataModels;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.UI.Shared.Exceptions;
@ -26,7 +25,6 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
private DataModelVisualizationViewModel _parent;
private DataModelPropertyAttribute _propertyDescription;
private PropertyInfo _propertyInfo;
private bool _isIgnored;
internal DataModelVisualizationViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, PropertyInfo propertyInfo)
{
@ -91,7 +89,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
}
}
public string PropertyPath
public virtual string PropertyPath
{
get
{
@ -106,7 +104,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
}
}
public string DisplayPropertyPath
public virtual string DisplayPropertyPath
{
get
{
@ -176,7 +174,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
IsMatchingFilteredTypes = false;
return;
}
if (looseMatch)
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(PropertyInfo.PropertyType));
else
@ -213,12 +211,14 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
if (IsRootViewModel)
{
var child = Children.FirstOrDefault(c => c.DataModel.PluginInfo.Guid == dataModelGuid);
var child = Children.FirstOrDefault(c => c.DataModel != null &&
c.DataModel.PluginInfo.Guid == dataModelGuid);
return child?.GetChildByPath(dataModelGuid, propertyPath);
}
else
{
var child = Children.FirstOrDefault(c => c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart);
var child = Children.FirstOrDefault(c => c.DataModel != null &&
c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart);
if (child == null)
return null;

View File

@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet">
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters">
<Style x:Key="DisplayConditionButton" TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatAccentBgButton}">
<Setter Property="Margin" Value="3 0" />
<Setter Property="Padding" Value="6 4" />
@ -90,7 +91,6 @@
Margin="15 0.5 0 0"
Visibility="{Binding ShowViewModel, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
</StackPanel.Resources>

View File

@ -30,11 +30,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.Abstract
public abstract void Update();
public virtual List<DataModelVisualizationViewModel> GetExtraDataModels()
public virtual DataModelPropertiesViewModel GetDataModelOverride()
{
if (Parent != null)
return Parent.GetExtraDataModels();
return new List<DataModelVisualizationViewModel>();
return Parent?.GetDataModelOverride();
}
public virtual void Delete()

View File

@ -152,13 +152,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
Update();
}
public override List<DataModelVisualizationViewModel> GetExtraDataModels()
public override DataModelPropertiesViewModel GetDataModelOverride()
{
var list = base.GetExtraDataModels();
if (SelectedListProperty != null)
list.Add(SelectedListProperty.ListTypePropertyViewModel);
return (DataModelPropertiesViewModel) SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService);
return list;
return base.GetDataModelOverride();
}
public override void Update()
@ -210,9 +209,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
return;
SelectedListProperty = dataModelListViewModel;
if (SelectedListProperty.ListTypePropertyViewModel == null)
SelectedListProperty.Update(_dataModelVisualizationService);
ApplyList();
}
}

View File

@ -202,6 +202,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
if (LeftSideDataModel == null || DisplayConditionPredicate.PredicateType == PredicateType.Dynamic && RightSideDataModel == null)
return;
if (GetDataModelOverride() != null)
LeftSideDataModel = GetDataModelOverride();
// If static, only allow selecting properties also supported by input
if (DisplayConditionPredicate.PredicateType == PredicateType.Static)
LeftSideDataModel.ApplyTypeFilter(false, _supportedInputTypes.ToArray());
@ -221,6 +224,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
if (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic)
{
SelectedRightSideProperty = LeftSideDataModel.GetChildForCondition(DisplayConditionPredicate, DisplayConditionSide.Right);
if (GetDataModelOverride() != null)
RightSideDataModel = GetDataModelOverride();
RightSideDataModel.ApplyTypeFilter(true, leftSideType);
}
else

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Plugins.Modules.General.DataModel.Windows;
namespace Artemis.Plugins.Modules.General.DataModel
@ -10,6 +11,12 @@ namespace Artemis.Plugins.Modules.General.DataModel
{
TimeDataModel = new TimeDataModel();
TestTimeList = new List<TimeDataModel>();
var testExpression = new Func<object, object, bool>((leftItem, rightDataModel) =>
((TimeDataModel) leftItem).CurrentTime.Month == ((GeneralDataModel) rightDataModel).TimeDataModel.CurrentTime.Day);
var test = TestTimeList.Any(model => testExpression(model, this));
}
public WindowDataModel ActiveWindow { get; set; }