浏览代码

Filter Lookup Filter Stuff

Kenric Nugteren 1 年之前
父节点
当前提交
17935e6aff

+ 8 - 0
InABox.Core/DigitalForms/DFUtils.cs

@@ -182,5 +182,13 @@ namespace InABox.Core
         {
             OnSave(typeof(TForm), form, entity);
         }
+
+        public static Type? FormEntityType(DigitalForm form)
+        {
+            return CoreUtils.TypeList(
+                AppDomain.CurrentDomain.GetAssemblies(),
+                x => string.Equals(x.Name, form.AppliesTo)
+            ).FirstOrDefault();
+        }
     }
 }

+ 74 - 3
InABox.Core/Filter.cs

@@ -8,6 +8,7 @@ using System.Reflection;
 using System.Runtime.Serialization;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
+using static InABox.Core.IFilter;
 
 namespace InABox.Core
 {
@@ -195,6 +196,21 @@ namespace InABox.Core
         }
     }
 
+    public class CustomFilterValue
+    {
+        public byte[] Data { get; set; }
+
+        public CustomFilterValue(byte[] data)
+        {
+            Data = data;
+        }
+
+        public override string ToString()
+        {
+            return "<Custom Value>";
+        }
+    }
+
     public interface IFilter : ISerializeBinary
     {
         Expression Expression { get; set; }
@@ -304,6 +320,14 @@ namespace InABox.Core
         void DeserializeBinary(CoreBinaryReader reader);
 
         IEnumerable<string> ColumnNames();
+
+        public delegate object? ConvertCustomValueFunction(IFilter filter, CustomFilterValue value);
+
+        /// <summary>
+        /// Walk through the filter and convert all values which are <see cref="CustomFilterValue"/> to normal objects.
+        /// </summary>
+        /// <param name="convert">A function used to convert each value.</param>
+        public void ConvertCustomValues(ConvertCustomValueFunction convert);
     }
 
     public static class Filter
@@ -1149,7 +1173,8 @@ namespace InABox.Core
             FilterConstant,
             Array,
             SubQuery,
-            ObjectArray
+            ObjectArray,
+            CustomValue
         }
 
         private class ValueTypeClass
@@ -1199,6 +1224,10 @@ namespace InABox.Core
             {
                 valueType = ValueType.SubQuery;
             }
+            else if(type == typeof(CustomFilterValue))
+            {
+                valueType = ValueType.CustomValue;
+            }
             else if (type == typeof(bool))
             {
                 valueType = ValueType.Boolean;
@@ -1305,6 +1334,11 @@ namespace InABox.Core
                     writer.Write(false);
                 }
             }
+            else if(value is CustomFilterValue customValue)
+            {
+                writer.Write(customValue.Data.Length);
+                writer.Write(customValue.Data);
+            }
             else if (value is bool bl)
             {
                 writer.Write(bl);
@@ -1407,6 +1441,8 @@ namespace InABox.Core
                     return ConvertToType(arrClass.ElementType)?.MakeArrayType();
                 case ValueType.SubQuery:
                     return typeof(ISubQuery);
+                case ValueType.CustomValue:
+                    return typeof(CustomFilterValue);
                 case ValueType.ObjectArray:
                     return typeof(object[]);
                 default:
@@ -1443,6 +1479,9 @@ namespace InABox.Core
                     return reader.ReadGuid();
                 case ValueType.FilterConstant:
                     return (FilterConstant)reader.ReadInt32();
+                case ValueType.CustomValue:
+                    var length = reader.ReadInt32();
+                    return new CustomFilterValue(reader.ReadBytes(length));
                 case ValueType.ObjectArray:
                     var nItems = reader.ReadInt32();
                     var objArr = new object?[nItems];
@@ -1648,12 +1687,35 @@ namespace InABox.Core
 
         #endregion
 
+        #region Custom Values
+
+        /// <summary>
+        /// Walk through the filter and convert all values which are <see cref="CustomFilterValue"/> to normal objects.
+        /// </summary>
+        /// <param name="convert">A function used to convert each value.</param>
+        public void ConvertCustomValues(ConvertCustomValueFunction convert)
+        {
+            if(Value is CustomFilterValue custom)
+            {
+                Value = convert(this, custom);
+            }
+            foreach(var and in Ands)
+            {
+                and.ConvertCustomValues(convert);
+            }
+            foreach (var or in Ors)
+            {
+                or.ConvertCustomValues(convert);
+            }
+        }
+
+        #endregion
     }
 
     public static class FilterSerialization
     {
         /// <summary>
-        /// Inverse of <see cref="FilterSerialization.Write{T}(CoreBinaryWriter, Filter{T}?)"/>.
+        /// Inverse of <see cref="Write{T}(CoreBinaryWriter, Filter{T}?)"/>.
         /// </summary>
         /// <param name="reader"></param>
         /// <returns></returns>
@@ -1669,7 +1731,7 @@ namespace InABox.Core
         }
 
         /// <summary>
-        /// Inverse of <see cref="FilterSerialization.ReadFilter{T}(CoreBinaryReader)"/>.
+        /// Inverse of <see cref="ReadFilter{T}(CoreBinaryReader)"/>.
         /// </summary>
         /// <param name="filter"></param>
         /// <param name="writer"></param>
@@ -1824,6 +1886,11 @@ namespace InABox.Core
                 writer.WritePropertyName("FilterConstant");
                 writer.WriteValue(val);
             }
+            else if(val is CustomFilterValue custom)
+            {
+                writer.WritePropertyName("CustomValue");
+                writer.WriteValue(custom.Data);
+            }
             else
             {
                 writer.WritePropertyName("Value");
@@ -1948,6 +2015,10 @@ namespace InABox.Core
             {
                 result.Value = CoreUtils.ChangeType<FilterConstant>(filterConstant);
             }
+            else if(data.TryGetValue("CustomValue", out var customValue))
+            {
+                result.Value = new CustomFilterValue(CoreUtils.ChangeType<byte[]>(customValue) ?? Array.Empty<byte>());
+            }
 
             if (data.ContainsKey("Ors"))
             {

+ 5 - 0
inabox.database.sqlite/SQLiteProvider.cs

@@ -1435,6 +1435,11 @@ namespace InABox.Database.SQLite
                 if (fieldmap.ContainsKey(prop))
                     prop = fieldmap[prop];
 
+                if(filter.Value is CustomFilterValue)
+                {
+                    throw new Exception("Custom Filter Value not able to be processed server-side!");
+                }
+
                 if (filter.Operator == Operator.InList || filter.Operator == Operator.NotInList)
                 {
                     // if, and only if the list contains Guids, we can safely bypass the 

+ 16 - 1
inabox.wpf/DigitalForms/Designer/Controls/Fields/DFLookupControl.cs

@@ -33,8 +33,23 @@ namespace InABox.DynamicGrid
                     columns.Add(property);
                 }
 
+                IFilter? filter;
+                if (!Field.Properties.Filter.IsNullOrWhiteSpace())
+                {
+                    filter = Serialization.Deserialize(typeof(Filter<>).MakeGenericType(type), Field.Properties.Filter) as IFilter;
+                    filter?.ConvertCustomValues((_, value) =>
+                    {
+                        var property = System.Text.Encoding.UTF8.GetString(value.Data);
+                        return FormDesignGrid.DataModel?.GetEntityValue(property);
+                    });
+                }
+                else
+                {
+                    filter = LookupFactory.DefineFilter(type);
+                }
+
                 var table = client.Query(
-                    LookupFactory.DefineFilter(type),
+                    filter,
                     columns,
                     LookupFactory.DefineSort(type)
                 );

+ 87 - 0
inabox.wpf/DigitalForms/DynamicVariableGrid.cs

@@ -2,10 +2,14 @@
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using System.Reflection;
 using System.Windows;
 using System.Windows.Controls;
 using InABox.Core;
+using InABox.Wpf;
 using InABox.WPF;
+using NPOI.OpenXmlFormats;
+using NPOI.SS.Formula.Functions;
 using NPOI.Util.Collections;
 
 namespace InABox.DynamicGrid
@@ -158,6 +162,66 @@ namespace InABox.DynamicGrid
         }
     }
 
+    public class DFLookupFilterNode : ComboBox, ICustomValueNode
+    {
+        public DFLookupFilterNode(Type entityType, CustomFilterValue? selectedValue)
+        {
+            var properties = CoreUtils.PropertyList(entityType, x => x.GetCustomAttribute<DoNotSerialize>() == null, true).Keys.ToList();
+            properties.Sort();
+            Items.Add("");
+            foreach (var property in properties)
+            {
+                Items.Add(property);
+            }
+
+            Value = selectedValue;
+
+            SelectionChanged += DFLookupFilterNode_SelectionChanged;
+
+            VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
+            VerticalContentAlignment = System.Windows.VerticalAlignment.Center;
+            MinWidth = 50;
+        }
+
+        private void DFLookupFilterNode_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            var text = SelectedItem as string;
+            if (text.IsNullOrWhiteSpace())
+            {
+                _value = null;
+            }
+            else
+            {
+                _value = new CustomFilterValue(System.Text.Encoding.UTF8.GetBytes(text));
+            }
+            ValueChanged?.Invoke(this, Value);
+        }
+
+        private CustomFilterValue? _value;
+        public CustomFilterValue? Value
+        {
+            get => _value;
+            set
+            {
+                if(value is not null)
+                {
+                    var text = System.Text.Encoding.UTF8.GetString(value.Data);
+                    SelectedItem = text;
+                    _value = value;
+                }
+                else
+                {
+                    SelectedItem = null;
+                    _value = null;
+                }
+            }
+        }
+
+        public FrameworkElement FrameworkElement => this;
+
+        public event ICustomValueNode.ValueChangedHandler? ValueChanged;
+    }
+
     public static class DynamicVariableUtils
     {
         public static bool CreateAndEdit(
@@ -186,6 +250,29 @@ namespace InABox.DynamicGrid
             var editor = new DynamicEditorForm(type);
             if (item is DFLayoutLookupFieldProperties)
             {
+                var appliesToType = DFUtils.FormEntityType(form);
+                editor.OnReconfigureEditors = grid =>
+                {
+                    var filter = grid.FindEditor("Filter");
+                    if(filter is FilterEditorControl filterEditor)
+                    {
+                        var config = new FilterEditorConfiguration();
+
+                        config.OnCreateCustomValueNode += (type, prop, op, value) =>
+                        {
+                            if (appliesToType is not null)
+                            {
+                                return new DFLookupFilterNode(appliesToType, value);
+                            }
+                            else
+                            {
+                                return null;
+                            }
+                        };
+
+                        filterEditor.Configuration = config;
+                    }
+                };
                 editor.OnFormCustomiseEditor += (sender, items, column, editor) => LookupEditor_OnFormCustomiseEditor(sender, variables, items, column, editor);
                 editor.OnEditorValueChanged += (sender, name, value) =>
                 {

+ 36 - 0
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterEditorConfiguration.cs

@@ -0,0 +1,36 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace InABox.Wpf
+{
+    public interface IFilterEditorConfiguration
+    {
+        public bool HasCustomValue { get; }
+
+        public ICustomValueNode? CreateCustomValueNode(Type T, string? propertyName, Operator op, CustomFilterValue? value);
+    }
+
+    public class FilterEditorConfiguration : IFilterEditorConfiguration
+    {
+        public delegate ICustomValueNode? CreateCustomValueNodeHandler(Type T, string? propertyName, Operator op, CustomFilterValue? value);
+        public event CreateCustomValueNodeHandler? OnCreateCustomValueNode;
+
+        public bool HasCustomValue { get => OnCreateCustomValueNode != null; }
+
+        public ICustomValueNode? CreateCustomValueNode(Type T, string? propertyName, Operator op, CustomFilterValue? value)
+        {
+            return OnCreateCustomValueNode?.Invoke(T, propertyName, op, value);
+        }
+
+        public static FilterEditorConfiguration Default()
+        {
+            return new FilterEditorConfiguration();
+        }
+    }
+}

+ 4 - 1
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterEditorControl.cs

@@ -1,4 +1,5 @@
 using InABox.Core;
+using InABox.Wpf;
 using Microsoft.CodeAnalysis.Differencing;
 using System;
 using System.Collections.Generic;
@@ -49,6 +50,8 @@ namespace InABox.DynamicGrid
             }
         }
 
+        public FilterEditorConfiguration Configuration { get; set; }
+
         public override int DesiredHeight()
         {
             return 25;
@@ -118,7 +121,7 @@ namespace InABox.DynamicGrid
 
         private void EditButton_Click(object sender, RoutedEventArgs e)
         {
-            var window = new FilterEditorWindow();
+            var window = new FilterEditorWindow(configuration: Configuration);
             var getMethod = typeof(FilterEditorWindow).GetMethod(nameof(FilterEditorWindow.GetFilter)).MakeGenericMethod(FilterType);
             var setMethod = typeof(FilterEditorWindow).GetMethod(nameof(FilterEditorWindow.SetFilter)).MakeGenericMethod(FilterType);
 

+ 4 - 2
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterEditorWindow.xaml.cs

@@ -11,15 +11,17 @@ namespace InABox.DynamicGrid
     public partial class FilterEditorWindow : ThemableWindow
     {
         private BaseFilterNode? FilterNode;
+        private FilterEditorConfiguration? Configuration;
 
-        public FilterEditorWindow()
+        public FilterEditorWindow(FilterEditorConfiguration? configuration = null)
         {
             InitializeComponent();
+            Configuration = configuration;
         }
 
         public void SetFilter<T>(Filter<T> filter)
         {
-            FilterNode = new FilterNode<T>(filter);
+            FilterNode = new FilterNode<T>(filter, configuration: Configuration);
             FilterNode.Margin = new Thickness(5);
 
             Content.Content = FilterNode;

+ 133 - 44
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterNode.cs

@@ -6,7 +6,9 @@ using System.Reflection;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
+using ControlzEx.Standard;
 using InABox.Core;
+using InABox.Wpf;
 
 namespace InABox.DynamicGrid;
 
@@ -19,31 +21,37 @@ public class FilterNode<T> : BaseFilterNode
     private OperatorNode Operator;
     private ValueNode Value;
     private Border ValuePlaceHolder;
-    private ConstantNode Constant;
+    private ConstantNode? Constant;
+    private ICustomValueNode? CustomValue;
     private Button AndButton;
     private Button OrButton;
     private FilterNodeGrid<T> Ands;
     private FilterNodeGrid<T> Ors;
 
+    private FilterEditorConfiguration Configuration;
+
     private FilterNodeType filterType;
 
     public delegate void RemoveEventHandler();
     public event RemoveEventHandler? Remove;
 
-    public FilterNode(Filter<T> filter, FilterNodeType filterType = FilterNodeType.ROOT)
+    public FilterNode(Filter<T> filter, FilterNodeType filterType = FilterNodeType.ROOT, FilterEditorConfiguration? configuration = null)
     {
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-
-        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto});
-        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto});
-        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto});
+        Configuration = configuration ?? FilterEditorConfiguration.Default();
+
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Clear
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Label
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Property
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Operator
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Constant
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // CustomValue
+        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // Value
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // And
+        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Or
+
+        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Header
+        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Ands
+        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Ors
 
         ClearButton = new Button { Content = "-", Width = 20, Margin = new Thickness(0, 0, 5, 0), Padding = new Thickness(5) };
 
@@ -74,10 +82,13 @@ public class FilterNode<T> : BaseFilterNode
         SetValueNode(valueNode ?? new EmptyValueNode(), filter.Value);
 
         Constant = CreateConstantNode(Property.SelectedProperty, filter.Operator, filter.Value as FilterConstant?);
-        ConstantChanged(Constant.SelectedConstant);
+        CheckValueVisibilities();
+
+        CustomValue = CreateCustomValueNode(Property.SelectedProperty, filter.Operator, filter.Value as CustomFilterValue);
+        CheckValueVisibilities();
 
-        Ands = new FilterNodeGrid<T>(filter.Ands, FilterNodeType.AND) { Margin = new Thickness(0, 0, 0, 0) };
-        Ors = new FilterNodeGrid<T>(filter.Ors, FilterNodeType.OR);
+        Ands = new FilterNodeGrid<T>(filter.Ands, FilterNodeType.AND, Configuration) { Margin = new Thickness(0, 0, 0, 0) };
+        Ors = new FilterNodeGrid<T>(filter.Ors, FilterNodeType.OR, Configuration);
 
         AndButton = new Button { Content = "And", Width = 40, Margin = new Thickness(0, 0, 5, 0), Padding = new Thickness(5) };
         OrButton = new Button { Content = "Or", Width = 40, Margin = new Thickness(0, 0, 0, 0), Padding = new Thickness(5) };
@@ -94,10 +105,17 @@ public class FilterNode<T> : BaseFilterNode
         }
         Add(Property, 0, 2);
         Add(Operator, 0, 3);
-        Add(Constant, 0, 4);
-        Add(ValuePlaceHolder, 0, 5);
-        Add(AndButton, 0, 6);
-        Add(OrButton, 0, 7);
+        if(Constant is not null)
+        {
+            Add(Constant, 0, 4);
+        }
+        if(CustomValue is not null)
+        {
+            Add(CustomValue.FrameworkElement, 0, 5);
+        }
+        Add(ValuePlaceHolder, 0, 6);
+        Add(AndButton, 0, 7);
+        Add(OrButton, 0, 8);
 
         Add(Ands, 1, 1, 7);
         Add(Ors, 2, 1, 7);
@@ -134,7 +152,7 @@ public class FilterNode<T> : BaseFilterNode
         var filter = new Filter<T>();
         filter.Expression = CoreUtils.CreateMemberExpression(typeof(T), Property.SelectedProperty);
         filter.Operator = Operator.SelectedOperator;
-        filter.Value = Constant.SelectedConstant ?? Value.Value;
+        filter.Value = Constant?.SelectedConstant ?? CustomValue?.Value ?? Value.Value;
 
         foreach(var and in Ands.GetFilters())
         {
@@ -224,14 +242,12 @@ public class FilterNode<T> : BaseFilterNode
         Value = node;
         Value.Value = value;
         Value.Margin = new Thickness(0, 0, 5, 0);
-        Add(Value, 0, 5);
-        CheckValueVisibility();
+        Add(Value, 0, 6);
+        CheckValueVisibilities();
     }
         
-    private ConstantNode CreateConstantNode(string? propertyName, Operator op, FilterConstant? constantvalue)
+    private ConstantNode? CreateConstantNode(string? propertyName, Operator op, FilterConstant? constantvalue)
     {
-        var result = new ConstantNode();
-
         var values = new Dictionary<string, FilterConstant?>();
         if (!string.IsNullOrWhiteSpace(propertyName))
         {
@@ -245,24 +261,53 @@ public class FilterNode<T> : BaseFilterNode
             }
         }
 
+        if (!values.Any())
+        {
+            return null;
+        }
+
+        var result = new ConstantNode();
+
         result.DisplayMemberPath = "Key";
         result.SelectedValuePath = "Value";
         result.ItemsSource = values;
         result.SelectedConstant = constantvalue; 
         result.Margin = new Thickness(0, 0, 5, 0);
-        result.Visibility = values.Any()
-            ? Visibility.Visible
-            : Visibility.Collapsed;
         result.ConstantChanged += ConstantChanged;
         return result;
     }
-        
-        
+
+    private ICustomValueNode? CreateCustomValueNode(string? propertyName, Operator op, CustomFilterValue? value)
+    {
+        if (!Configuration.HasCustomValue)
+        {
+            return null;
+        }
+        var customValue = Configuration.CreateCustomValueNode(typeof(T), propertyName, op, value);
+        if(customValue is not null)
+        {
+            customValue.FrameworkElement.Margin = new Thickness(0, 0, 5, 0);
+            customValue.ValueChanged += CustomValue_ValueChanged;
+        }
+        return customValue;
+    }
+
+    private void CustomValue_ValueChanged(ICustomValueNode sender, CustomFilterValue? value)
+    {
+        CheckValueVisibilities();
+    }
+
     private void Property_PropertyChanged(string? property)
     {
-        Children.Remove(Constant);
+        if (Constant is not null)
+        {
+            Children.Remove(Constant);
+        }
         Constant = CreateConstantNode(property, Operator.SelectedOperator, null);
-        Add(Constant,0,4);
+        if(Constant is not null)
+        {
+            Add(Constant, 0, 4);
+        }
             
         var value = Value.Value;
         Children.Remove(Value);
@@ -274,14 +319,31 @@ public class FilterNode<T> : BaseFilterNode
             value
         );
 
+        CustomFilterValue? customValue = null;
+        if(CustomValue is not null)
+        {
+            customValue = CustomValue.Value;
+            Children.Remove(CustomValue.FrameworkElement);
+        }
+        CustomValue = CreateCustomValueNode(property, Operator.SelectedOperator, customValue);
+        if (CustomValue is not null)
+        {
+            Add(CustomValue.FrameworkElement, 0, 5);
+        }
     }
 
     private void Operator_OperatorChanged(Operator op)
     {
 
-        Children.Remove(Constant);
+        if (Constant is not null)
+        {
+            Children.Remove(Constant);
+        }
         Constant = CreateConstantNode(Property.SelectedProperty, Operator.SelectedOperator, null);
-        Add(Constant,0,4);
+        if (Constant is not null)
+        {
+            Add(Constant, 0, 4);
+        }
         
         var value = Value.Value;
         Children.Remove(Value);
@@ -299,17 +361,44 @@ public class FilterNode<T> : BaseFilterNode
 
     private void ConstantChanged(FilterConstant? constant)
     {
-        CheckValueVisibility();
+        CheckValueVisibilities();
     }
 
-    private void CheckValueVisibility()
+    private void CheckValueVisibilities()
     {
-        Value.Visibility = (Constant?.SelectedConstant == null) && Value is not EmptyValueNode
-            ? Visibility.Visible
-            : Visibility.Collapsed;
-        ValuePlaceHolder.Visibility = (Constant?.SelectedConstant != null) || (Value is EmptyValueNode)
-            ? Visibility.Visible
-            : Visibility.Collapsed;
+        if(Constant?.SelectedConstant != null)
+        {
+            Value.Visibility = Visibility.Collapsed;
+            ValuePlaceHolder.Visibility = Visibility.Visible;
+            Constant.Visibility = Visibility.Visible;
+            if(CustomValue is not null)
+            {
+                CustomValue.FrameworkElement.Visibility = Visibility.Collapsed;
+            }
+        }
+        else if(CustomValue?.Value != null)
+        {
+            Value.Visibility = Visibility.Collapsed;
+            ValuePlaceHolder.Visibility = Visibility.Visible;
+            if(Constant is not null)
+            {
+                Constant.Visibility = Visibility.Collapsed;
+            }
+            CustomValue.FrameworkElement.Visibility = Visibility.Visible;
+        }
+        else
+        {
+            Value.Visibility = Visibility.Visible;
+            ValuePlaceHolder.Visibility = Visibility.Collapsed;
+            if (Constant is not null)
+            {
+                Constant.Visibility = Visibility.Visible;
+            }
+            if (CustomValue is not null)
+            {
+                CustomValue.FrameworkElement.Visibility = Visibility.Visible;
+            }
+        }
     }
 
     private void Add(UIElement element, int row, int column = 1, int columnSpan = 1)

+ 6 - 2
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterNodeGrid.cs

@@ -3,17 +3,21 @@ using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
 using InABox.Core;
+using InABox.Wpf;
 
 namespace InABox.DynamicGrid;
 
 public class FilterNodeGrid<T> : Grid
 {
     private List<FilterNode<T>> Filters;
+    private FilterEditorConfiguration Configuration;
 
     public FilterNodeType FilterType;
 
-    public FilterNodeGrid(List<Filter<T>> filters, FilterNodeType filterType)
+    public FilterNodeGrid(List<Filter<T>> filters, FilterNodeType filterType, FilterEditorConfiguration configuration)
     {
+        Configuration = configuration;
+
         ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
 
         FilterType = filterType;
@@ -45,7 +49,7 @@ public class FilterNodeGrid<T> : Grid
     {
         RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
 
-        var element = new FilterNode<T>(filter, FilterType)
+        var element = new FilterNode<T>(filter, FilterType, configuration: Configuration)
         {
             Margin = new Thickness(0, 5, 0, 0)
         };

+ 0 - 68
inabox.wpf/DynamicGrid/Editors/FilterEditor/Nodes/ConstantNode.cs

@@ -34,72 +34,4 @@ public class ConstantNode : ComboBox
         get => (SelectedItem != null) ? ((KeyValuePair<String,FilterConstant?>)SelectedItem).Value : null;
         set => SelectedValue = value;
     }
-}
-
-public class CustomValueNode : DockPanel
-{
-    private object? _value;
-
-    public FilterConstant[]? Constants { get; set; }
-    
-    public String[]? CustomValues { get; set; }
-    
-    private readonly TextBox _text;
-    private readonly Button _button;
-    
-    public delegate void CustomValueChangedHandler(object value);
-
-    public event CustomValueChangedHandler? CustomValueChanged;
-    
-    public CustomValueNode()
-    {
-
-        _value = null;
-        
-        VerticalAlignment = VerticalAlignment.Stretch;
-        HorizontalAlignment = HorizontalAlignment.Stretch;
-        
-        _button = new Button()
-        {
-            VerticalContentAlignment = VerticalAlignment.Center,
-            Content = "..",
-            Padding = new Thickness(10,0,10,0),
-            Command = new ActionCommand(ShowMenu)
-        };
-        _button.SetValue(DockProperty, Dock.Left);
-        Children.Add(_button);
-
-        _text = new TextBox()
-        {
-            VerticalContentAlignment = VerticalAlignment.Center,
-            Margin = new Thickness(0,0,5,0),
-            Visibility = Visibility.Collapsed
-        };
-        _text.SetValue(DockProperty, Dock.Left);
-        Children.Add(_text);
-        
-
-    }
-
-    private void ShowMenu()
-    {
-        ContextMenu menu = new ContextMenu();
-        bool headers = Constants?.Any() == true && CustomValues?.Any() == true;
-        if (headers)
-        {
-            
-        }
-        else
-        {
-        }
-        MenuItem constantheader = headers
-        ? new MenuItem() { };
-        ContextMenu menu = new ContextMenu();
-    }
-
-    public object? Value
-    {
-        get => _value;
-        set => _value = value;
-    }
 }

+ 23 - 0
inabox.wpf/DynamicGrid/Editors/FilterEditor/Nodes/CustomValueNode.cs

@@ -0,0 +1,23 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace InABox.Wpf
+{
+    public interface ICustomValueNode
+    {
+        CustomFilterValue? Value { get; set; }
+
+        /// <summary>
+        /// Get the framework element for this node; in many cases, this will just link to <see langword="this"/> (assuming that the implementing class is a <see cref="System.Windows.FrameworkElement"/>).
+        /// </summary>
+        FrameworkElement FrameworkElement { get; }
+
+        public delegate void ValueChangedHandler(ICustomValueNode sender, CustomFilterValue? value);
+        event ValueChangedHandler? ValueChanged;
+    }
+}