Browse Source

Added DirectEdit to Tree grids

Kenric Nugteren 2 weeks ago
parent
commit
2482d98788

+ 12 - 0
InABox.Core/CoreTreeNodes.cs

@@ -62,6 +62,11 @@ namespace InABox.Core
         public object? this[string column]
         {
             get => Row[column];
+            set
+            {
+                Row[column] = value;
+                _owner.DoColumnChanged(this, column);
+            }
         }
 
         public event PropertyChangedEventHandler? PropertyChanged;
@@ -184,5 +189,12 @@ namespace InABox.Core
             }
         }
         
+        public delegate void ColumnChangedEventHandler(CoreTreeNode node, string column);
+        public event ColumnChangedEventHandler? ColumnChanged;
+
+        internal void DoColumnChanged(CoreTreeNode node, string column)
+        {
+            ColumnChanged?.Invoke(node, column);
+        }
     }
 }

+ 4 - 4
inabox.wpf/DynamicGrid/UIComponent/DynamicGridGridUIComponent.cs

@@ -1133,8 +1133,6 @@ public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
 
                 var newColumn = newcol.CreateGridColumn();
 
-                newColumn.AllowEditing = newcol.Editable && Parent.IsDirectEditMode();
-
                 var summary = newcol.Summary();
                 if (summary != null)
                     Summaries.Add(summary);
@@ -1633,8 +1631,10 @@ public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
     {
         if (e.Key == Key.OemPeriod)
         {
-            var editor = e.OriginalSource as TimeSpanEdit;
-            if (editor != null && editor.SelectionStart < 2) editor.SelectionStart = 3;
+            if (e.OriginalSource is TimeSpanEdit editor && editor.SelectionStart < 2)
+            {
+                editor.SelectionStart = 3;
+            }
         }
         else if (e.Key == Key.Tab)
         {

+ 266 - 5
inabox.wpf/DynamicGrid/UIComponent/DynamicGridTreeUIComponent.cs

@@ -241,6 +241,12 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         _tree.FilterLevel = FilterLevel.Extended;
         _tree.SelectionForeground = DynamicGridUtils.SelectionForeground;
         _tree.SelectionBackground = DynamicGridUtils.SelectionBackground;
+
+        _tree.EditTrigger = EditTrigger.OnTap;
+        _tree.CurrentCellBeginEdit += _tree_CurrentCellBeginEdit;
+        _tree.CurrentCellEndEdit += _tree_CurrentCellEndEdit;
+        _tree.CurrentCellDropDownSelectionChanged += _tree_CurrentCellDropDownSelectionChanged;
+        _tree.PreviewKeyUp += _tree_PreviewKeyUp;
         
         _tree.ColumnSizer = TreeColumnSizer.None;
         _tree.RowHeight = 30D;
@@ -276,12 +282,17 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
 
     #region Input
 
-    private CoreRow? GetRowFromIndex(int rowIndex)
+    private CoreTreeNode? GetNodeFromIndex(int rowIndex)
     {
         // Syncfusion has given us the row index, so it also will give us the correct row, after sorting.
         // Hence, here we use the syncfusion DataGrid.GetRecordAtRowIndex, which *should* always return a DataRowView.
         var row = _tree.GetNodeAtRowIndex(rowIndex);
-        return MapRow((row.Item as CoreTreeNode)?.Row);
+        return row.Item as CoreTreeNode;
+    }
+
+    private CoreRow? GetRowFromIndex(int rowIndex)
+    {
+        return MapRow(GetNodeFromIndex(rowIndex)?.Row);
     }
 
     private void _tree_CellDoubleTapped(object? sender, TreeGridCellDoubleTappedEventArgs e)
@@ -493,6 +504,16 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
     {
         ColumnsMenu.Visibility = Parent.Options.SelectColumns ? Visibility.Visible : Visibility.Hidden;
 
+        var allowEditing = Parent.IsDirectEditMode();
+        var reloadColumns = false;
+
+        if (_tree.AllowEditing != allowEditing)
+        {
+            _tree.NavigationMode = allowEditing ? NavigationMode.Cell : NavigationMode.Row;
+            _tree.AllowEditing = allowEditing;
+            reloadColumns = true;
+        }
+
         _tree.AllowFiltering = Parent.Options.FilterRows;
 
         if (Parent.Options.DragSource)
@@ -515,7 +536,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         _tree.AllowDrop = Parent.Options.DragTarget;
         _tree.SelectionMode = Parent.Options.MultiSelect ? GridSelectionMode.Extended : GridSelectionMode.Single;
 
-        return false;
+        return reloadColumns;
     }
 
     private void _tree_CellToolTipOpening(object? sender, TreeGridCellToolTipOpeningEventArgs e)
@@ -1040,6 +1061,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
     public CoreTreeNodes Nodes { get; set; }
 
     private CoreTable? _innerTable;
+    private bool _invalidating = false;
 
     public void BeforeRefresh()
     {
@@ -1055,6 +1077,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         _innerTable.LoadColumns(data.Columns);
 
         for (var i = 0; i < ActionColumns.Count; i++)
+        {
             _innerTable.Columns.Add(
                 new CoreColumn
                 {
@@ -1063,6 +1086,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
                         ? typeof(BitmapImage)
                         : typeof(String)
                 });
+        }
 
         foreach (var row in data.Rows)
         {
@@ -1074,6 +1098,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
             var _parent = row.Get<Guid>(ParentColumn.Property);
             nodes.Add(_id, _parent, newRow);
         }
+        nodes.ColumnChanged += Nodes_ColumnChanged;
         Nodes = nodes;
         _tree.ItemsSource = nodes.Nodes;
 
@@ -1086,6 +1111,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
     public void AddPage(IEnumerable<CoreRow> page)
     {
         if (_innerTable is null) return;
+        _invalidating = true;
 
         foreach(var row in page)
         {
@@ -1100,6 +1126,8 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
 
         CalculateRowHeight();
         UpdateRecordCount();
+
+        _invalidating = false;
     }
 
     private void ProcessRow(CoreRow innerRow, CoreRow row)
@@ -1180,12 +1208,15 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
     public void InvalidateRow(CoreRow row)
     {
         if (_innerTable is null || row.Index < 0 || row.Index >= _innerTable.Rows.Count) return;
+        _invalidating = true;
 
         var _innerRow = _innerTable.Rows[row.Index];
         ProcessRow(_innerRow, row);
 
         var coreTreeNode = Nodes.Find(_innerRow);
         coreTreeNode?.InvalidateData();
+
+        _invalidating = false;
     }
 
 
@@ -1194,16 +1225,246 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         _tree.ScrollInView(new RowColumnIndex(row.Index + 1, 0));
     }
 
+    private CoreTreeNode? GetNode(CoreRow row)
+    {
+        if (_innerTable is null || row.Index < 0 || row.Index >= _innerTable.Rows.Count) return null;
+
+        var _innerRow = _innerTable.Rows[row.Index];
+        var node = Nodes.Find(_innerRow);
+        return node;
+    }
+
     public void UpdateCell(CoreRow row, string column, object? value)
     {
-        throw new NotImplementedException();
+        var node = GetNode(row);
+        if(node is not null)
+        {
+            node[column] = value;
+            node.InvalidateData();
+        }
     }
 
     public void UpdateRow(CoreRow row)
     {
-        throw new NotImplementedException();
+        var dataRow = GetNode(row);
+        if(dataRow is not null)
+        {
+            foreach(var (key, value) in row)
+            {
+                dataRow[key] = value;
+            }
+            for (var i = 0; i < ActionColumns.Count; i++)
+                dataRow[$"_ActionColumn{i}"] = ActionColumns[i].Data(row);
+            dataRow.InvalidateData();
+        }
     }
 
+    #region Direct Edit
+
+    private void _tree_PreviewKeyUp(object sender, KeyEventArgs e)
+    {
+        if (e.Key == Key.OemPeriod)
+        {
+            if (e.OriginalSource is Syncfusion.Windows.Shared.TimeSpanEdit editor && editor.SelectionStart < 2)
+            {
+                editor.SelectionStart = 3;
+            }
+        }
+        else if (e.Key == Key.Tab)
+        {
+            if (Parent.IsDirectEditMode())
+            {
+                _tree.SelectionController.CurrentCellManager.EndEdit();
+                _tree.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
+                _tree.SelectionController.CurrentCellManager.BeginEdit();
+                e.Handled = true;
+            }
+        }
+    }
+
+    private bool bChanged;
+
+    private class DirectEditingObject
+    {
+        public T Object { get; set; }
+
+        public CoreRow Row { get; set; }
+
+        public CoreTreeNode? Node { get; set; }
+
+        public DirectEditingObject(T obj, CoreRow row, CoreTreeNode? node)
+        {
+            Object = obj;
+            Row = row;
+            Node = node;
+        }
+    }
+
+    private DirectEditingObject? _editingObject;
+
+    private DirectEditingObject EnsureEditingObject(CoreRow row)
+    {
+        _editingObject ??= new(Parent.LoadItem(row), row, GetNode(row));
+        return _editingObject;
+    }
+
+    private void UpdateData(string column, Dictionary<CoreColumn, object?> updates)
+    {
+        if (_editingObject is null)
+            return;
+
+        var coreRow = _editingObject.Row;
+
+        try
+        {
+            Parent.UpdateData(_editingObject.Object, coreRow, column, updates);
+        }
+        catch(Exception e)
+        {
+            MessageWindow.ShowError($"Error saving {typeof(T)}", e);
+        }
+    }
+    private void UpdateData(CoreTreeNode node, int columnIndex)
+    {
+        if (GetColumn(columnIndex) is DynamicGridColumn gridcol)
+        {
+            var datacol = Parent.Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(gridcol.ColumnName));
+            if (datacol != null)
+            {
+                var value = node?[datacol.ColumnName];
+                if (value is null)
+                    value = CoreUtils.GetDefault(datacol.DataType);
+                else
+                    value = CoreUtils.ChangeType(value, datacol.DataType);
+
+                UpdateData(datacol.ColumnName, new Dictionary<CoreColumn, object?>() { { datacol, value } });
+            }
+        }
+    }
+
+    private Dictionary<string, CoreTable> _lookups = new();
+    private void _tree_CurrentCellBeginEdit(object? sender, TreeGridCurrentCellBeginEditEventArgs e)
+    {
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+        if (row is null)
+            return;
+
+        EnsureEditingObject(row);
+
+        if (_tree.Columns[e.RowColumnIndex.ColumnIndex] is TreeGridComboBoxColumn column && column.ItemsSource == null)
+        {
+            var gridColumn = GetColumn(e.RowColumnIndex.ColumnIndex);
+            if(gridColumn is DynamicGridColumn col)
+            {
+                var property = col.ColumnName;
+                var prop = CoreUtils.GetProperty(typeof(T), property);
+                var editor = prop.GetEditor();
+                if (editor is ILookupEditor lookupEditor)
+                {
+                    if (!_lookups.ContainsKey(property))
+                        _lookups[property] = lookupEditor.Values(typeof(T), property);
+                    var combo = column;
+                    combo.ItemsSource = _lookups[property].ToDictionary(_lookups[property].Columns[0].ColumnName, "Display");
+                    combo.SelectedValuePath = "Key";
+                    combo.DisplayMemberPath = "Value";
+                }
+            }
+
+        }
+
+        bChanged = false;
+    }
+
+    private void Nodes_ColumnChanged(CoreTreeNode node, string column)
+    {
+        if (_invalidating) return;
+
+        var row = GetRow(node);
+        if (row is null)
+            return;
+
+        var data = Parent.Data;
+
+        var dataCol = Parent.Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(column));
+        var col = ColumnList.OfType<DynamicGridColumn>()
+            .FirstOrDefault(x => x.ColumnName.Equals(column));
+        
+        if (col is null || dataCol is null)
+            return;
+
+        if (col is DynamicGridCheckBoxColumn<T>)
+        {
+            EnsureEditingObject(row);
+            if(_editingObject is not null)
+            {
+                var value = node[column];
+
+                _invalidating = true;
+                UpdateData(column, new Dictionary<CoreColumn, object?>() { { dataCol, value } });
+                _invalidating = false;
+            }
+
+            _editingObject = null;
+        }
+        if (_editingObject is not null)
+            bChanged = true;
+    }
+
+    private void _tree_CurrentCellDropDownSelectionChanged(object? sender, CurrentCellDropDownSelectionChangedEventArgs e)
+    {
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+        if (row is null)
+            return;
+        EnsureEditingObject(row);
+        if ((_editingObject is not null) && (e.SelectedItem is Tuple<object?, string> tuple))
+        {
+            var gridColumn = GetColumn(e.RowColumnIndex.ColumnIndex);
+            if (gridColumn is DynamicGridColumn col)
+            {
+                var corecol = col.ColumnName;
+
+                var updates = new Dictionary<CoreColumn, object?>();
+
+                var prefix = string.Join(".", corecol.Split(".").Reverse().Skip(1).Reverse());
+                var field = corecol.Split(".").Last();
+
+                var prop = CoreUtils.GetProperty(typeof(T), corecol);
+                if (prop.GetEditor() is ILookupEditor editor)
+                {
+                    var data = editor.Values(typeof(T), corecol);
+                    var lookuprow = data.Rows.FirstOrDefault(r => Equals(r[field], tuple.Item1))
+                        ?? data.NewRow(true);
+
+                    foreach (CoreColumn lookupcol in data.Columns)
+                    {
+                        var columnname = String.IsNullOrWhiteSpace(prefix)
+                            ? lookupcol.ColumnName
+                            : String.Join(".", prefix, lookupcol.ColumnName);
+                        var updatecol = Parent.Data.Columns.FirstOrDefault(x => String.Equals(x.ColumnName, columnname));
+                        if (updatecol != null)
+                            updates[updatecol] = lookuprow[lookupcol.ColumnName];
+                    }
+                    UpdateData(corecol, updates);
+                    bChanged = true;
+                }
+            }
+        }
+    }
+
+    private void _tree_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
+    {
+        if (_editingObject is not null && bChanged)
+        {
+            UpdateData(_editingObject.Node, e.RowColumnIndex.ColumnIndex);
+        }
+        if (bChanged)
+            Parent.DoChanged();
+        bChanged = false;
+        _editingObject = null;
+    }
+
+    #endregion
+
     #region Drag + Drop
 
     private void _tree_DragOver(object sender, DragEventArgs e)