Jelajahi Sumber

Expression Editor improvements with tooltip; CoreExpression generating variables list from type and generator;
Dimensions Formula and Format now have expression editor;

Kenric Nugteren 2 tahun lalu
induk
melakukan
9637e5c78f

+ 31 - 6
InABox.Core/CoreExpression.cs

@@ -19,16 +19,32 @@ namespace InABox.Core
 
         public string Name { get; set; }
 
-        public List<string> Parameters { get; set; }
+        public string? Description { get; set; }
+
+        public List<Parameter> Parameters { get; set; }
 
         public FncType Function { get; set; }
 
-        public CoreExpressionFunction(string group, string name, FncType function, List<string> parameters)
+        public class Parameter
+        {
+            public string Name { get; set; }
+
+            public string Type { get; set; }
+
+            public Parameter(string name, string type)
+            {
+                Name = name;
+                Type = type;
+            }
+        }
+
+        public CoreExpressionFunction(string group, string name, FncType function, List<Parameter> parameters, string? description = null)
         {
             Group = group;
             Name = name;
             Parameters = parameters;
             Function = function;
+            Description = description;
         }
     }
 
@@ -79,9 +95,9 @@ namespace InABox.Core
             {
                 if (!(p[0].Evaluate(v) is string format)) throw new Exception("No format string given for Format()");
                 return string.Format(format, p.Skip(1).Select(x => x.Evaluate(v)).ToArray());
-            }, "fmt", "...");
+            }, "string fmt", "...");
 
-            RegisterFunction("Other", "Client_LoadDocument", Fnc_LoadDocument, "docID");
+            RegisterFunction("Other", "Client_LoadDocument", "Loads a byte array from the database Document table, with the ID {docID}.", Fnc_LoadDocument, "Guid docID");
         }
 
         private static object? Fnc_LoadDocument(IExpression[] p, IDictionary<string, object> v)
@@ -100,8 +116,17 @@ namespace InABox.Core
                 ?.Get<Document, byte[]>(x => x.Data);
         }
 
-        public static void RegisterFunction(string group, string name, FncType function, params string[] parameters) =>
-            Functions.Add(new CoreExpressionFunction(group, name, function, parameters.ToList()));
+        public static void RegisterFunction(string group, string name, FncType function, params string[] parameters)
+            => RegisterFunction(group, name, null, function, parameters);
+
+        public static void RegisterFunction(string group, string name, string? description, FncType function, params string[] parameters) =>
+            Functions.Add(new CoreExpressionFunction(group, name, function, parameters.Select(x =>
+            {
+                var parts = x.Split(' ');
+                if (parts.Length == 1)
+                    return new CoreExpressionFunction.Parameter(parts[0], "");
+                return new CoreExpressionFunction.Parameter(parts[1], parts[0]);
+            }).ToList(), description));
 
         #endregion
     }

+ 24 - 8
InABox.Core/Dimensions/DimensionUnit.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace InABox.Core
 {
@@ -20,11 +21,11 @@ namespace InABox.Core
         
         [CheckBoxEditor]
         [EditorSequence(4)]
-        public virtual bool HasLength { get; set; }        
+        public virtual bool HasLength { get; set; }
         
         [CheckBoxEditor]
         [EditorSequence(5)]
-        public virtual bool HasWidth { get; set; }        
+        public virtual bool HasWidth { get; set; }
         
         [CheckBoxEditor]
         [EditorSequence(6)]
@@ -34,13 +35,13 @@ namespace InABox.Core
         [EditorSequence(7)]
         public virtual bool HasWeight { get; set; }
 
-        [TextBoxEditor]
+        [ExpressionEditor(null, typeof(DimensionsExpressionModelGenerator))]
         [EditorSequence(8)]
-        public virtual String Formula { get; set; }        
+        public virtual string Formula { get; set; }        
         
-        [TextBoxEditor]
+        [ExpressionEditor(null, typeof(DimensionsExpressionModelGenerator))]
         [EditorSequence(9)]
-        public virtual String Format { get; set; }
+        public virtual string Format { get; set; }
 
         [NullEditor]
         public long Sequence { get; set; }
@@ -96,7 +97,22 @@ namespace InABox.Core
 
             return result;
         }
-        
-        
+
+        private class DimensionsExpressionModelGenerator : IExpressionModelGenerator
+        {
+            public List<string> GetVariables(object?[] items)
+            {
+                var dimensionUnits = items.Select(x => x as IDimensionUnit).Where(x => x != null).Cast<IDimensionUnit>();
+                var variables = new List<string>();
+
+                if (dimensionUnits.All(x => x.HasQuantity)) variables.Add("Quantity");
+                if (dimensionUnits.All(x => x.HasLength)) variables.Add("Length");
+                if (dimensionUnits.All(x => x.HasWidth)) variables.Add("Width");
+                if (dimensionUnits.All(x => x.HasHeight)) variables.Add("Height");
+                if (dimensionUnits.All(x => x.HasWeight)) variables.Add("Weight");
+
+                return variables;
+            }
+        }
     }
 }

+ 36 - 4
InABox.Core/Editors/ExpressionEditor.cs

@@ -6,6 +6,11 @@ using System.Text;
 
 namespace InABox.Core
 {
+    public interface IExpressionModelGenerator
+    {
+        public List<string> GetVariables(object?[] items);
+    }
+
     public class ExpressionEditor : BaseEditor
     {
         /// <summary>
@@ -13,20 +18,47 @@ namespace InABox.Core
         /// </summary>
         public Type? ModelType { get; set; }
 
+        private IExpressionModelGenerator? _modelGenerator;
+
+        public Type? ModelGenerator { get; }
+
         [DoNotSerialize]
         [JsonIgnore]
         public List<string>? VariableNames { get; set; }
 
-        public ExpressionEditor(Type? modelType)
+        public ExpressionEditor(Type? modelType, Type? modelGenerator = null)
         {
-            if (modelType != null && !typeof(IExpressionModel).IsAssignableFrom(modelType))
-                throw new Exception($"Expression type must be a {nameof(IExpressionModel)}");
             ModelType = modelType;
+            if(modelGenerator != null)
+            {
+                if (!typeof(IExpressionModelGenerator).IsAssignableFrom(modelGenerator))
+                {
+                    throw new Exception($"Model generator must be a {nameof(IExpressionModelGenerator)}");
+                }
+                ModelGenerator = modelGenerator;
+            }
         }
 
         protected override BaseEditor DoClone()
         {
-            return new ExpressionEditor(ModelType);
+            return new ExpressionEditor(ModelType, ModelGenerator)
+            {
+                VariableNames = VariableNames
+            };
+        }
+
+        public List<string> GetVariables(object?[] items)
+        {
+            if (VariableNames != null)
+                return VariableNames;
+            if (ModelGenerator != null)
+            {
+                _modelGenerator ??= (Activator.CreateInstance(ModelGenerator) as IExpressionModelGenerator)!;
+                return _modelGenerator.GetVariables(items);
+            }
+            if(ModelType != null)
+                return CoreExpression.GetModelVariables(ModelType);
+            return new List<string>();
         }
     }
 }

+ 2 - 0
InABox.DynamicGrid/DynamicEditorForm.xaml.cs

@@ -280,6 +280,8 @@ namespace InABox.DynamicGrid
             Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
             Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
 
+            Editor.GetItems += () => _items;
+
             Pages = pages;
 
             if (Pages == null || Pages.Count == 0)

+ 15 - 1
InABox.DynamicGrid/DynamicEditorGrid.xaml.cs

@@ -44,6 +44,8 @@ namespace InABox.DynamicGrid
 
         public delegate void SetPropertyValueHandler(object sender, string name, object value);
 
+        public delegate object?[] GetItemsEvent();
+
         // Column Definitions as defined by calling model
         private DynamicGridColumns _columns;
 
@@ -150,6 +152,8 @@ namespace InABox.DynamicGrid
         public event FindDocumentEvent? OnFindDocument;
         public event SaveDocumentEvent? OnSaveDocument;
 
+        public event GetItemsEvent? GetItems;
+
         private void DynamicEditorGrid_Loaded(object sender, RoutedEventArgs e)
         {
             ConfigureEditors();
@@ -203,6 +207,11 @@ namespace InABox.DynamicGrid
             return "";
         }
 
+        private void ConfigureExpressionEditor(ExpressionEditorControl control)
+        {
+            control.GetItems += () => GetItems?.Invoke() ?? Array.Empty<object?>();
+        }
+
         private void ConfigurePopupEditor(PopupEditorControl popup, DynamicGridColumn column, PopupEditor editor)
         {
             popup.ColumnName = column.ColumnName;
@@ -389,6 +398,11 @@ namespace InABox.DynamicGrid
                             ConfigurePasswordEditor(passwordEditorControl, column, passwordEditor);
                         }
 
+                        else if(Editor is ExpressionEditorControl expressionEditorControl && editor is ExpressionEditor expressionEditor)
+                        {
+                            ConfigureExpressionEditor(expressionEditorControl);
+                        }
+
                         Editor.Configure();
                         if (!editors.Any(x => x.ColumnName.Equals(Editor.ColumnName)))
                             editors.Add(Editor);
@@ -548,7 +562,7 @@ namespace InABox.DynamicGrid
                         TimestampEditor => new TimestampEditorControl(),
                         ColorEditor => new ColorEditorControl(),
                         FilterEditor filter => new FilterEditorControl { FilterType = filter.Type! },
-                        ExpressionEditor expression => new ExpressionEditorControl { ModelType = expression.ModelType, Variables = expression.VariableNames },
+                        ExpressionEditor expression => new ExpressionEditorControl(expression),
                         _ => null,
                     };
                     if (element != null)

+ 11 - 19
InABox.DynamicGrid/Editors/ExpressionEditor/ExpressionEditorControl.cs

@@ -13,14 +13,19 @@ namespace InABox.DynamicGrid
 {
     public class ExpressionEditorControl : DynamicEditorControl<string>
     {
-        private List<string>? _variables;
-
         private TextBox TextBox = null!;
         private Button Edit = null!;
 
-        public Type? ModelType { get; set; }
+        public ExpressionEditor Editor { get; set; }
+
+        public delegate object?[] GetItemsEvent();
+
+        public event GetItemsEvent? GetItems;
 
-        public List<string>? Variables { get; set; }
+        public ExpressionEditorControl(ExpressionEditor editor)
+        {
+            Editor = editor;
+        }
 
         public override int DesiredHeight()
         {
@@ -83,23 +88,10 @@ namespace InABox.DynamicGrid
             return grid;
         }
 
-        private List<string> GetVariables()
-        {
-            if (_variables is not null)
-                return _variables;
-
-            if (Variables is not null)
-                return _variables = Variables;
-
-            if (ModelType is null)
-                return _variables = new List<string>();
-
-            return _variables = CoreExpression.GetModelVariables(ModelType);
-        }
-
         private void EditButton_Click(object sender, RoutedEventArgs e)
         {
-            var window = new ExpressionEditorWindow(GetVariables());
+            var variables = Editor.GetVariables(GetItems?.Invoke() ?? Array.Empty<object?>());
+            var window = new ExpressionEditorWindow(variables);
             window.Expression = TextBox.Text;
             if(window.ShowDialog() == true)
             {

+ 22 - 2
InABox.DynamicGrid/Editors/ExpressionEditor/ExpressionEditorWindow.xaml

@@ -8,6 +8,15 @@
         Title="Expression Editor" Height="650" Width="800"
         Loaded="ExpressionWindow_Loaded"
         x:Name="ExpressionWindow">
+    <Window.Resources>
+        <Style x:Key="tooltipDescriptionStyle" TargetType="TextBlock">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Path=Description}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+    </Window.Resources>
     <Grid Margin="5">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*"/>
@@ -37,7 +46,7 @@
                             <ItemsControl ItemsSource="{x:Static local:ExpressionEditorWindow.FunctionTemplates}">
                                 <ItemsControl.ItemTemplate>
                                     <DataTemplate>
-                                        <Border BorderThickness="1,0,0,1" BorderBrush="LightGray"
+                                        <Border BorderThickness="1,0,0,1" BorderBrush="WhiteSmoke"
                                                 Padding="5">
                                             <Expander Header="{Binding Item1}">
                                                 <ListBox ItemsSource="{Binding Item2}"
@@ -54,7 +63,18 @@
                                                                        FontFamily="Consolas" FontSize="12"
                                                                        Padding="5"
                                                                        Tag="{Binding}"
-                                                                       MouseLeftButtonUp="FunctionTemplate_Click"/>
+                                                                       MouseLeftButtonUp="FunctionTemplate_Click">
+                                                                <TextBlock.ToolTip>
+                                                                    <ToolTip Background="LightGray">
+                                                                        <StackPanel>
+                                                                            <TextBlock Text="{Binding Tooltip}"
+                                                                                       FontFamily="Consolas" FontSize="12"/>
+                                                                            <TextBlock Text="{Binding Description}" FontSize="12"
+                                                                                       Style="{StaticResource tooltipDescriptionStyle}"/>
+                                                                        </StackPanel>
+                                                                    </ToolTip>
+                                                                </TextBlock.ToolTip>
+                                                            </TextBlock>
                                                         </DataTemplate>
                                                     </ListBox.ItemTemplate>
                                                 </ListBox>

+ 87 - 64
InABox.DynamicGrid/Editors/ExpressionEditor/ExpressionEditorWindow.xaml.cs

@@ -34,7 +34,7 @@ namespace InABox.DynamicGrid
             set => Editor.Text = value;
         }
 
-        private static void RegisterFunctionTemplate(string group, string name, List<string> parameters)
+        private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
         {
             var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
             if(groupList is null)
@@ -46,76 +46,95 @@ namespace InABox.DynamicGrid
             groupList.Add(new FunctionTemplate
             {
                 Name = name,
-                Parameters = parameters.ToList()
+                Parameters = parameters,
+                Description = description ?? ""
             });
         }
         private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
-            => RegisterFunctionTemplate(group, name, parameters.ToList());
+            => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
+            {
+                var parts = x.Split(' ');
+                if (parts.Length == 1)
+                {
+                    return new FunctionParameter(parts[0], "");
+                }
+                return new FunctionParameter(parts[1], parts[0]);
+            }).ToList<IFunctionParameter>());
+        private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
+            => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
+            {
+                var parts = x.Split(' ');
+                if (parts.Length == 1)
+                {
+                    return new FunctionParameter(parts[0], "");
+                }
+                return new FunctionParameter(parts[1], parts[0]);
+            }).ToList<IFunctionParameter>());
 
         static ExpressionEditorWindow()
         {
-            RegisterFunctionTemplate("Math", "Abs", "x");
-            RegisterFunctionTemplate("Math", "Acos", "x");
-            RegisterFunctionTemplate("Math", "Asin", "x");
-            RegisterFunctionTemplate("Math", "Atan", "x");
-            RegisterFunctionTemplate("Math", "Ceiling", "x");
-            RegisterFunctionTemplate("Math", "Cos", "x");
-            RegisterFunctionTemplate("Math", "Count", "x");
-            RegisterFunctionTemplate("Math", "Exp", "exp");
-            RegisterFunctionTemplate("Math", "Floor", "x");
-            RegisterFunctionTemplate("Math", "IEEERemainder", "x", "y");
-            RegisterFunctionTemplate("Math", "Log10", "x");
-            RegisterFunctionTemplate("Math", "Log", "x", "base");
-            RegisterFunctionTemplate("Math", "Max", "x", "y");
-            RegisterFunctionTemplate("Math", "Min", "x", "y");
-            RegisterFunctionTemplate("Math", "Pow", "base", "exp");
+            RegisterFunctionTemplate("Math", "Abs", "double x");
+            RegisterFunctionTemplate("Math", "Acos", "double x");
+            RegisterFunctionTemplate("Math", "Asin", "double x");
+            RegisterFunctionTemplate("Math", "Atan", "double x");
+            RegisterFunctionTemplate("Math", "Ceiling", "double x");
+            RegisterFunctionTemplate("Math", "Cos", "double x");
+            RegisterFunctionTemplate("Math", "Count", "...");
+            RegisterFunctionTemplate("Math", "Exp", "double exp");
+            RegisterFunctionTemplate("Math", "Floor", "double x");
+            RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
+            RegisterFunctionTemplate("Math", "Log10", "double x");
+            RegisterFunctionTemplate("Math", "Log", "double x", "double base");
+            RegisterFunctionTemplate("Math", "Max", "double x", "double y");
+            RegisterFunctionTemplate("Math", "Min", "double x", "double y");
+            RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
             RegisterFunctionTemplate("Math", "Random");
-            RegisterFunctionTemplate("Math", "Round", "x", "precision");
-            RegisterFunctionTemplate("Math", "Sign", "x");
-            RegisterFunctionTemplate("Math", "Sin", "x");
-            RegisterFunctionTemplate("Math", "Sqrt", "x");
-            RegisterFunctionTemplate("Math", "Tan", "x");
-            RegisterFunctionTemplate("Math", "Truncate", "x");
-
-            RegisterFunctionTemplate("Logical", "If", "condition", "exp1", "exp2");
-            RegisterFunctionTemplate("Logical", "In", "value", "...");
-
-            RegisterFunctionTemplate("Statistical", "Average", "...");
-            RegisterFunctionTemplate("Statistical", "Mean", "...");
-            RegisterFunctionTemplate("Statistical", "Median", "...");
-            RegisterFunctionTemplate("Statistical", "Mode", "...");
-            RegisterFunctionTemplate("Statistical", "Sum", "...");
-
-            RegisterFunctionTemplate("String", "Contains", "text", "value");
-            RegisterFunctionTemplate("String", "EndsWith", "text", "value");
-            RegisterFunctionTemplate("String", "Length", "value");
-            RegisterFunctionTemplate("String", "PadLeft", "value", "length", "character");
-            RegisterFunctionTemplate("String", "PadRight", "value", "length", "character");
-            RegisterFunctionTemplate("String", "StartsWith", "text", "value");
-            RegisterFunctionTemplate("String", "Substring", "text", "startIndex", "length");
-
-            RegisterFunctionTemplate("Date", "AddYears", "date", "years");
-            RegisterFunctionTemplate("Date", "AddMonths", "date", "months");
-            RegisterFunctionTemplate("Date", "AddDays", "date", "days");
-            RegisterFunctionTemplate("Date", "AddHours", "date", "hrs");
-            RegisterFunctionTemplate("Date", "AddMinutes", "date", "mins");
-            RegisterFunctionTemplate("Date", "AddSeconds", "date", "secs");
-            RegisterFunctionTemplate("Date", "AddMilliseconds", "date", "ms");
-            RegisterFunctionTemplate("Date", "YearOf", "date");
-            RegisterFunctionTemplate("Date", "MonthOf", "date");
-            RegisterFunctionTemplate("Date", "DayOf", "date");
-            RegisterFunctionTemplate("Date", "HourOf", "date");
-            RegisterFunctionTemplate("Date", "MinuteOf", "date");
-            RegisterFunctionTemplate("Date", "SecondOf", "date");
-            RegisterFunctionTemplate("Date", "MillisecondOf", "date");
-            RegisterFunctionTemplate("Date", "DaysBetween", "date", "date");
-            RegisterFunctionTemplate("Date", "HoursBetween", "date", "date");
-            RegisterFunctionTemplate("Date", "MinutesBetween", "date", "date");
-            RegisterFunctionTemplate("Date", "SecondsBetween", "date", "date");
-            RegisterFunctionTemplate("Date", "MillisecondsBetween", "date", "date");
+            RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
+            RegisterFunctionTemplate("Math", "Sign", "double x");
+            RegisterFunctionTemplate("Math", "Sin", "double x");
+            RegisterFunctionTemplate("Math", "Sqrt", "double x");
+            RegisterFunctionTemplate("Math", "Tan", "double x");
+            RegisterFunctionTemplate("Math", "Truncate", "double x");
+
+            RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
+            RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
+
+            RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
+            RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
+            RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
+            RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
+            RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
+
+            RegisterFunctionTemplate("String", "Contains", "string text", "str value");
+            RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
+            RegisterFunctionTemplate("String", "Length", "string value");
+            RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
+            RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
+            RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
+            RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
+
+            RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
+            RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
+            RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
+            RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
+            RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
+            RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
+            RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
+            RegisterFunctionTemplate("Date", "YearOf", "date date");
+            RegisterFunctionTemplate("Date", "MonthOf", "date date");
+            RegisterFunctionTemplate("Date", "DayOf", "date date");
+            RegisterFunctionTemplate("Date", "HourOf", "date date");
+            RegisterFunctionTemplate("Date", "MinuteOf", "date date");
+            RegisterFunctionTemplate("Date", "SecondOf", "date date");
+            RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
+            RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
+            RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
+            RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
+            RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
+            RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
 
             RegisterFunctionTemplate("Conversion", "Date", "value");
-            RegisterFunctionTemplate("Conversion", "Date", "value", "format");
+            RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
             RegisterFunctionTemplate("Conversion", "Decimal", "value");
             RegisterFunctionTemplate("Conversion", "Double", "value");
             RegisterFunctionTemplate("Conversion", "Integer", "value");
@@ -124,7 +143,11 @@ namespace InABox.DynamicGrid
 
             foreach(var function in CoreExpression.Functions)
             {
-                RegisterFunctionTemplate(function.Group, function.Name, function.Parameters);
+                RegisterFunctionTemplate(
+                    function.Group,
+                    function.Name,
+                    function.Description,
+                    function.Parameters.Select(x => new FunctionParameter(x.Name, x.Type)).ToList<IFunctionParameter>());
             }
         }
 
@@ -218,7 +241,7 @@ namespace InABox.DynamicGrid
 
             var placeholder = DeletePlaceholder();
             InsertText($"{template.Name}(");
-            AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x}}}")) + ")");
+            AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
             if (placeholder)
                 JumpPlaceholder(true);
         }

+ 37 - 2
InABox.DynamicGrid/Editors/ExpressionEditor/FunctionTemplate.cs

@@ -6,12 +6,47 @@ using System.Threading.Tasks;
 
 namespace InABox.DynamicGrid
 {
+    public interface IFunctionParameter
+    {
+        string Name { get; }
+
+        string ParamType { get; }
+    }
+
+    public class FunctionParameter : IFunctionParameter
+    {
+        public string Name { get; set; }
+
+        public string ParamType { get; set; }
+
+        public FunctionParameter(string name, string paramType)
+        {
+            Name = name;
+            ParamType = paramType;
+        }
+    }
+    public class FunctionParameter<TParam> : IFunctionParameter
+    {
+        public string Name { get; set; }
+
+        public string ParamType => typeof(TParam).ToString();
+
+        public FunctionParameter(string name)
+        {
+            Name = name;
+        }
+    }
+
     public class FunctionTemplate
     {
         public string Name { get; set; }
 
-        public List<string> Parameters { get; set; }
+        public string Description { get; set; }
+
+        public List<IFunctionParameter> Parameters { get; set; }
+
+        public string Template => $"{Name}({string.Join(", ", Parameters.Select(x => x.Name))})";
 
-        public string Template => $"{Name}({string.Join(", ", Parameters)})";
+        public string Tooltip => $"{Name}({string.Join(", ", Parameters.Select(x => string.IsNullOrWhiteSpace(x.ParamType) ? x.Name : $"{x.ParamType} {x.Name}"))})";
     }
 }