From 1e315b5c9a0191bb5936208eb60831d5e1bf675f Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Thu, 10 Mar 2022 16:22:53 -0800 Subject: [PATCH] Add GridColumn.DisplayIndex and Grid.ColumnOrderChanged Gtk: Improve performance of TreeGridView drastically. Mac: Fix alignment of some controls on CustomCell Updated GridViewSection to allow saving/restoring Fixes #1868 --- .vscode/launch.json | 36 +- lib/monomac | 2 +- src/Eto.Gtk/Forms/Cells/CellHandler.cs | 4 +- .../Forms/Cells/CheckBoxCellHandler.cs | 12 +- .../Forms/Cells/ComboBoxCellHandler.cs | 14 +- src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs | 21 +- .../Forms/Cells/DrawableCellHandler.cs | 17 +- .../Forms/Cells/ImageTextCellHandler.cs | 16 +- .../Forms/Cells/ImageViewCellHandler.cs | 13 +- .../Forms/Cells/ProgressCellHandler.cs | 14 +- src/Eto.Gtk/Forms/Cells/TextBoxCellHandler.cs | 13 +- .../Forms/Controls/GridColumnHandler.cs | 46 +- src/Eto.Gtk/Forms/Controls/GridHandler.cs | 91 ++-- src/Eto.Gtk/Forms/Controls/GridViewHandler.cs | 4 +- src/Eto.Gtk/Forms/Controls/GtkTreeModel.cs | 10 +- .../Forms/Controls/TreeGridViewHandler.cs | 119 ++--- src/Eto.Mac/Forms/Cells/CustomCellHandler.cs | 4 +- .../Forms/Controls/GridColumnHandler.cs | 40 +- src/Eto.Mac/Forms/Controls/GridHandler.cs | 40 +- src/Eto.Mac/Forms/Controls/GridViewHandler.cs | 12 + .../Forms/Controls/TreeGridViewHandler.cs | 15 +- .../Forms/Controls/GridColumnHandler.cs | 6 + .../Forms/Controls/GridHandler.cs | 29 ++ .../Forms/Controls/TreeGridViewHandler.cs | 2 +- .../Forms/Controls/GridColumnHandler.cs | 6 + src/Eto.Wpf/Forms/Controls/GridHandler.cs | 36 +- src/Eto/Forms/Controls/Grid.cs | 38 ++ src/Eto/Forms/Controls/GridColumn.cs | 24 +- src/Eto/Forms/Controls/TreeGridStore.cs | 74 +++ test/Eto.Test/MainForm.cs | 4 +- .../Sections/Controls/GridViewSection.cs | 444 +++++++++++------- .../Sections/Controls/TreeGridViewSection.cs | 318 +++++-------- test/Eto.Test/Settings.cs | 19 + 33 files changed, 989 insertions(+), 554 deletions(-) create mode 100644 src/Eto/Forms/Controls/TreeGridStore.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index bcc9f86262..0adaa50bd8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ // "targetArchitecture": "x86_64", // uncomment to test intel on M1 "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Mac64 - mono", @@ -22,7 +23,8 @@ "passDebugOptionsViaEnvironmentVariable": true, "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.XamMac2", @@ -32,7 +34,8 @@ "program": "${workspaceFolder}/artifacts/test/${config:var.configuration}/net6.0-macos/Eto.Test.XamMac2.app/Contents/MacOS/Eto.Test.XamMac2", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.XamMac2 - mono", @@ -44,7 +47,8 @@ "passDebugOptionsViaEnvironmentVariable": true, "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Gtk", @@ -60,7 +64,8 @@ }, "console": "internalConsole", "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false, }, { "name": "Eto.Test.Gtk - mono", @@ -76,7 +81,8 @@ } }, "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Gtk2", @@ -87,7 +93,8 @@ "passDebugOptionsViaEnvironmentVariable": true, "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Wpf", @@ -98,7 +105,8 @@ "targetArchitecture": "x86_64", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Wpf - .NET 4.8", @@ -108,7 +116,8 @@ "program": "${workspaceFolder}/artifacts/test/${config:var.configuration}/net48/Eto.Test.Wpf.exe", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.WinForms", @@ -118,7 +127,8 @@ "program": "${workspaceFolder}/artifacts/test/${config:var.configuration}/net6.0-windows/Eto.Test.WinForms.exe", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.WinForms - .NET 4.8", @@ -128,7 +138,8 @@ "program": "${workspaceFolder}/artifacts/test/${config:var.configuration}/net48/Eto.Test.WinForms.exe", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Test.Direct2D", @@ -138,7 +149,8 @@ "program": "${workspaceFolder}/artifacts/test/${config:var.configuration}/net6.0-windows/Eto.Test.Direct2D.exe", "args": [], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "justMyCode": false }, { "name": "Eto.Addin.VisualStudio.Mac", diff --git a/lib/monomac b/lib/monomac index 2520c256ae..bf33732843 160000 --- a/lib/monomac +++ b/lib/monomac @@ -1 +1 @@ -Subproject commit 2520c256aeb3976210b33fc0d4d1c665a5250251 +Subproject commit bf337328430b062ed0adc2010540fe3b72e2803f diff --git a/src/Eto.Gtk/Forms/Cells/CellHandler.cs b/src/Eto.Gtk/Forms/Cells/CellHandler.cs index 66e532cd8d..ed1641f8c5 100644 --- a/src/Eto.Gtk/Forms/Cells/CellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/CellHandler.cs @@ -7,8 +7,6 @@ namespace Eto.GtkSharp.Forms.Cells { public interface ICellDataSource { - object GetItem(int row); - object GetItem(Gtk.TreePath path); void EndCellEditing(Gtk.TreePath path, int column); @@ -22,6 +20,7 @@ public interface ICellDataSource void OnCellFormatting(GridCellFormatEventArgs args); int RowDataColumn { get; } + int ItemDataColumn { get; } } public interface IEtoCellRenderer @@ -102,6 +101,7 @@ protected virtual void BindCell(ref int dataIndex) //if (FormattingEnabled) { Column.Control.AddAttribute(Control, "row", Source.RowDataColumn); + Column.Control.AddAttribute(Control, "item", Source.ItemDataColumn); } } diff --git a/src/Eto.Gtk/Forms/Cells/CheckBoxCellHandler.cs b/src/Eto.Gtk/Forms/Cells/CheckBoxCellHandler.cs index c472810961..5895e483d6 100644 --- a/src/Eto.Gtk/Forms/Cells/CheckBoxCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/CheckBoxCellHandler.cs @@ -21,8 +21,18 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } diff --git a/src/Eto.Gtk/Forms/Cells/ComboBoxCellHandler.cs b/src/Eto.Gtk/Forms/Cells/ComboBoxCellHandler.cs index 8b783a67fd..ada31b8c0e 100644 --- a/src/Eto.Gtk/Forms/Cells/ComboBoxCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/ComboBoxCellHandler.cs @@ -26,10 +26,22 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkTextCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } + #if GTK2 public override void GetSize(Gtk.Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height) diff --git a/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs b/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs index 813eb48eec..90a9699277 100644 --- a/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs @@ -98,14 +98,25 @@ public int Row set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } IGtkCellEditable CreateEditable(Gdk.Rectangle cellArea) { - var item = Handler.Source.GetItem(Row); + var item = Item; int column = -1; var args = new CellEventArgs(null, Handler.Widget, Row, column, item, CellStates.Editing, null); @@ -149,7 +160,7 @@ protected override void Render(Gdk.Drawable window, Gtk.Widget widget, Gdk.Recta } using (var graphics = new Graphics(new GraphicsHandler(widget, window))) { - var item = h.Source.GetItem(Row); + var item = Item; var args = new CellPaintEventArgs(graphics, cell_area.ToEto(), flags.ToEto(), item); h.Callback.OnPaint(h.Widget, args); } @@ -170,7 +181,7 @@ protected override void OnGetPreferredWidth(Gtk.Widget widget, out int minimum_s var h = Handler; if (h == null) return; - var item = h.Source?.GetItem(Row); + var item = Item; int column = -1; var args = new CellEventArgs(null, h.Widget, Row, column, item, CellStates.Editing, null); @@ -215,7 +226,7 @@ protected override unsafe void OnRender(Cairo.Context cr, Gtk.Widget widget, Gdk } using (var graphics = new Graphics(new GraphicsHandler(widget, cr))) { - var item = Handler.Source.GetItem(Row); + var item = Item; var args = new CellPaintEventArgs(graphics, cell_area.ToEto(), flags.ToEto(), item); Handler.Callback.OnPaint(Handler.Widget, args); } diff --git a/src/Eto.Gtk/Forms/Cells/DrawableCellHandler.cs b/src/Eto.Gtk/Forms/Cells/DrawableCellHandler.cs index 7a8c817b71..a86dd253ff 100644 --- a/src/Eto.Gtk/Forms/Cells/DrawableCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/DrawableCellHandler.cs @@ -20,8 +20,19 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } @@ -36,7 +47,7 @@ protected override void Render(Gdk.Drawable window, Gtk.Widget widget, Gdk.Recta { using (var graphics = new Graphics(new GraphicsHandler(widget, window))) { - var item = Handler.Source.GetItem(Row); + var item = Item; #pragma warning disable 618 var args = new DrawableCellPaintEventArgs(graphics, cell_area.ToEto(), flags.ToEto(), item); Handler.Callback.OnPaint(Handler.Widget, args); @@ -54,7 +65,7 @@ protected override void OnRender (Cairo.Context cr, Gtk.Widget widget, Gdk.Recta { using (var graphics = new Graphics(new GraphicsHandler(cr, null, false))) { - var item = Handler.Source.GetItem(Row); + var item = Item; #pragma warning disable 618 var args = new DrawableCellPaintEventArgs(graphics, cell_area.ToEto(), flags.ToEto(), item); Handler.Callback.OnPaint(Handler.Widget, args); diff --git a/src/Eto.Gtk/Forms/Cells/ImageTextCellHandler.cs b/src/Eto.Gtk/Forms/Cells/ImageTextCellHandler.cs index cbb943b236..b0417589b0 100644 --- a/src/Eto.Gtk/Forms/Cells/ImageTextCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/ImageTextCellHandler.cs @@ -15,8 +15,6 @@ class ImageRenderer : Gtk.CellRendererPixbuf { public ImageTextCellHandler Handler { get; set; } - [GLib.Property("item")] - public object Item { get; set; } int row; [GLib.Property("row")] @@ -25,8 +23,19 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } } @@ -108,6 +117,7 @@ protected override void BindCell(ref int dataIndex) if (FormattingEnabled) { Column.Control.AddAttribute(imageCell, "row", Source.RowDataColumn); + Column.Control.AddAttribute(imageCell, "item", Source.ItemDataColumn); } } diff --git a/src/Eto.Gtk/Forms/Cells/ImageViewCellHandler.cs b/src/Eto.Gtk/Forms/Cells/ImageViewCellHandler.cs index e06975db6a..0892aa6db8 100644 --- a/src/Eto.Gtk/Forms/Cells/ImageViewCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/ImageViewCellHandler.cs @@ -19,8 +19,19 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } diff --git a/src/Eto.Gtk/Forms/Cells/ProgressCellHandler.cs b/src/Eto.Gtk/Forms/Cells/ProgressCellHandler.cs index 109ef51584..8b5e80cffa 100644 --- a/src/Eto.Gtk/Forms/Cells/ProgressCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/ProgressCellHandler.cs @@ -21,10 +21,22 @@ public int Row get { return row; } set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } + #if GTK2 public override void GetSize(Gtk.Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height) diff --git a/src/Eto.Gtk/Forms/Cells/TextBoxCellHandler.cs b/src/Eto.Gtk/Forms/Cells/TextBoxCellHandler.cs index 2dc720ece7..fb84f90f2d 100644 --- a/src/Eto.Gtk/Forms/Cells/TextBoxCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/TextBoxCellHandler.cs @@ -33,8 +33,19 @@ public int Row set { row = value; + } + } + + object item; + [GLib.Property("item")] + public object Item + { + get { return item; } + set + { + item = value; if (Handler.FormattingEnabled) - Handler.Format(new GtkTextCellFormatEventArgs(this, Handler.Column.Widget, Handler.Source.GetItem(Row), Row)); + Handler.Format(new GtkGridCellFormatEventArgs(this, Handler.Column.Widget, item, Row)); } } diff --git a/src/Eto.Gtk/Forms/Controls/GridColumnHandler.cs b/src/Eto.Gtk/Forms/Controls/GridColumnHandler.cs index e95e835e0b..85814a3c55 100644 --- a/src/Eto.Gtk/Forms/Controls/GridColumnHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/GridColumnHandler.cs @@ -11,20 +11,11 @@ public interface IGridHandler bool IsEventHandled(string handler); void ColumnClicked(GridColumnHandler column); + int GetColumnDisplayIndex(GridColumnHandler column); + void SetColumnDisplayIndex(GridColumnHandler column, int index); } - public interface IGridColumnHandler - { - Gtk.TreeViewColumn Control { get; } - - GLib.Value GetValue(object dataItem, int dataColumn, int row); - - void BindCell(IGridHandler grid, ICellDataSource source, int columnIndex, ref int dataIndex); - - void SetupEvents(); - } - - public class GridColumnHandler : WidgetHandler, GridColumn.IHandler, IGridColumnHandler + public class GridColumnHandler : WidgetHandler, GridColumn.IHandler { Cell dataCell; bool autoSize; @@ -126,7 +117,7 @@ public bool Visible set { Control.Visible = value; } } - public void BindCell(IGridHandler grid, ICellDataSource source, int columnIndex, ref int dataIndex) + public void SetupCell(IGridHandler grid, ICellDataSource source, int columnIndex, ref int dataIndex) { this.grid = grid; if (dataCell != null) @@ -140,6 +131,7 @@ public void BindCell(IGridHandler grid, ICellDataSource source, int columnIndex, SetCellAttributes(); cellhandler.BindCell(source, this, columnIndex, ref dataIndex); } + SetupEvents(); } public void SetupEvents() @@ -182,11 +174,6 @@ public GLib.Value GetValue(object dataItem, int dataColumn, int row) return new GLib.Value((string)null); } - Gtk.TreeViewColumn IGridColumnHandler.Control - { - get { return Control; } - } - public bool Expand { get => Control.Expand; @@ -215,6 +202,29 @@ public int MaxWidth GridHandler?.Tree?.ColumnsAutosize(); } } + + int? displayIndex; + + public int DisplayIndex + { + get => GridHandler?.GetColumnDisplayIndex(this) ?? displayIndex ?? -1; + set + { + if (GridHandler != null) + GridHandler.SetColumnDisplayIndex(this, value); + else + displayIndex = value; + } + } + + internal void SetDisplayIndex() + { + if (displayIndex != null && GridHandler != null) + { + GridHandler.SetColumnDisplayIndex(this, displayIndex.Value); + displayIndex = null; + } + } } } diff --git a/src/Eto.Gtk/Forms/Controls/GridHandler.cs b/src/Eto.Gtk/Forms/Controls/GridHandler.cs index 9f78165b4d..a140a07246 100644 --- a/src/Eto.Gtk/Forms/Controls/GridHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/GridHandler.cs @@ -12,6 +12,7 @@ class GridHandler { internal static readonly object Border_Key = new object(); internal static readonly object RowDataColumn_Key = new object(); + internal static readonly object ItemDataColumn_Key = new object(); internal static readonly object AllowColumnReordering_Key = new object(); internal static readonly object AllowEmptySelection_Key = new object(); } @@ -21,6 +22,7 @@ public abstract class GridHandler : GtkControl columnMap = new Dictionary(); @@ -103,6 +105,10 @@ protected void UpdateModel() protected override void Initialize() { Tree = new Gtk.TreeView(); + // always need this one so the last column doesn't expand + Tree.AppendColumn(spacingColumn = new Gtk.TreeViewColumn()); + Tree.ColumnDragFunction = Connector.HandleColumnDrag; + UpdateModel(); Tree.HeadersVisible = true; Control.Add(Tree); @@ -118,7 +124,7 @@ protected override void Initialize() } - protected new GridConnector Connector { get { return (GridConnector)base.Connector; } } + protected new GridConnector Connector => (GridConnector)base.Connector; protected override WeakConnector CreateConnector() { @@ -240,6 +246,23 @@ public virtual void OnTreeButtonPress(object sender, Gtk.ButtonPressEventArgs e) h.Callback.OnCellClick(h.Widget, new GridCellMouseEventArgs(column, rowIndex, columnIndex, item, e.Event.ToEtoMouseButtons(), e.Event.State.ToEtoKey(), loc)); } + + [GLib.ConnectBefore] + public virtual void HandleGridColumnsChanged(object sender, EventArgs e) + { + var h = Handler; + if (h == null) + return; + h.Callback.OnColumnOrderChanged(h.Widget, new GridColumnEventArgs(h.Widget.Columns.First())); + } + + public virtual bool HandleColumnDrag(Gtk.TreeView tree_view, Gtk.TreeViewColumn column, Gtk.TreeViewColumn prev_column, Gtk.TreeViewColumn next_column) + { + var h = Handler; + if (h == null) + return true; + return prev_column != h.spacingColumn; + } } public override void AttachEvent(string id) @@ -268,6 +291,9 @@ public override void AttachEvent(string id) case Grid.SelectionChangedEvent: Tree.Selection.Changed += Connector.HandleGridSelectionChanged; break; + case Grid.ColumnOrderChangedEvent: + Tree.ColumnsChanged += Connector.HandleGridColumnsChanged; + break; default: base.AttachEvent(id); break; @@ -278,7 +304,6 @@ public override void AttachEvent(string id) public override void OnLoadComplete(EventArgs e) { base.OnLoadComplete(e); - Tree.AppendColumn(new Gtk.TreeViewColumn()); UpdateColumns(); } @@ -286,17 +311,14 @@ void SetupColumnEvents() { if (!Widget.Loaded) return; - foreach (var col in Widget.Columns.Select(r => r.Handler).OfType()) + foreach (var col in Widget.Columns.Select(r => r.Handler).OfType()) { col.SetupEvents(); } } - public int RowDataColumn - { - get { return Widget.Properties.Get(GridHandler.RowDataColumn_Key); } - private set { Widget.Properties.Set(GridHandler.RowDataColumn_Key, value); } - } + public int RowDataColumn => 0; + public int ItemDataColumn => 1; protected virtual void UpdateColumns() { @@ -304,15 +326,23 @@ protected virtual void UpdateColumns() return; columnMap.Clear(); int columnIndex = 0; - int dataIndex = 0; - RowDataColumn = dataIndex++; - - foreach (var col in Widget.Columns.Select(r => r.Handler).OfType()) + int dataIndex = 2; // skip RowDataColumn and ItemDataColumn + + for (int i = 0; i < Widget.Columns.Count; i++) { - col.Control.Reorderable = AllowColumnReordering; - col.BindCell(this, this, columnIndex++, ref dataIndex); - col.SetupEvents(); + var col = Widget.Columns[i]; + var colHandler = (GridColumnHandler)col.Handler; + colHandler.Control.Reorderable = AllowColumnReordering; + colHandler.SetupCell(this, this, columnIndex++, ref dataIndex); + if (i == 0) + Tree.ExpanderColumn = colHandler.Control; + } + + foreach (var col in Widget.Columns.OrderBy(r => r.DisplayIndex)) + { + ((GridColumnHandler)col.Handler).SetDisplayIndex(); } + } class ColumnCollection : EnumerableChangedHandler @@ -322,17 +352,14 @@ class ColumnCollection : EnumerableChangedHandler 0) - Handler.Tree.InsertColumn(colhandler.Control, index); - else - Handler.Tree.AppendColumn(colhandler.Control); + Handler.Tree.InsertColumn(colhandler.Control, index); Handler.UpdateColumns(); } @@ -349,6 +376,7 @@ public override void RemoveAllItems() { Handler.Tree.RemoveColumn(col); } + Handler.Tree.AppendColumn(Handler.spacingColumn); Handler.UpdateColumns(); } @@ -363,10 +391,10 @@ public bool ShowHeader public bool AllowColumnReordering { - get { return Widget.Properties.Get(GridHandler.AllowColumnReordering_Key, true); } + get { return Widget.Properties.Get(GridHandler.AllowColumnReordering_Key, false); } set { - Widget.Properties.Set(GridHandler.AllowColumnReordering_Key, value, true); + Widget.Properties.Set(GridHandler.AllowColumnReordering_Key, value, false); UpdateColumns(); } } @@ -381,11 +409,6 @@ public int NumberOfColumns public abstract object GetItem(Gtk.TreePath path); - public object GetItem(int row) - { - return GetItem(GetPathAtRow(row)); - } - public int GetColumnOfItem(Gtk.TreeViewColumn item) { return Widget.Columns.Select(r => r.Handler as GridColumnHandler).Select(r => r.Control).ToList().IndexOf(item); @@ -649,6 +672,20 @@ protected void EnsureSelection() SelectRow(0); } } + + public int GetColumnDisplayIndex(GridColumnHandler column) + { + var columns = Tree.Columns; + return Array.IndexOf(columns, column.Control); + } + + public void SetColumnDisplayIndex(GridColumnHandler column, int index) + { + var columns = Tree.Columns; + var currentIndex = Array.IndexOf(columns, column.Control); + if (index != currentIndex) + Tree.MoveColumnAfter(column.Control, columns[index]); + } } } diff --git a/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs b/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs index 32d98b5f4c..df47044f0f 100755 --- a/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs @@ -145,10 +145,12 @@ public GLib.Value GetColumnValue(object item, int dataColumn, int row) { if (dataColumn == RowDataColumn) return new GLib.Value(row); + if (dataColumn == ItemDataColumn) + return new GLib.Value(item); int column; if (ColumnMap.TryGetValue(dataColumn, out column)) { - var colHandler = (IGridColumnHandler)Widget.Columns[column].Handler; + var colHandler = (GridColumnHandler)Widget.Columns[column].Handler; return colHandler.GetValue(item, dataColumn, row); } return new GLib.Value((string)null); diff --git a/src/Eto.Gtk/Forms/Controls/GtkTreeModel.cs b/src/Eto.Gtk/Forms/Controls/GtkTreeModel.cs index 570d203651..50626c3049 100755 --- a/src/Eto.Gtk/Forms/Controls/GtkTreeModel.cs +++ b/src/Eto.Gtk/Forms/Controls/GtkTreeModel.cs @@ -23,7 +23,7 @@ public IGtkTreeModelHandler Handler set => handler = new WeakReference(value); } - class Node + public class Node { public TItem Item { get; set; } @@ -99,6 +99,12 @@ IEnumerable GetParents(TItem item) } } + public int GetRowIndexOfIter(Gtk.TreeIter iter) + { + var node = GetNodeAtIter(iter); + return Handler.DataStore.GetRowOfIndexPath(node.Indices); + } + public Gtk.TreePath GetPathFromItem(TItem item) { var path = new Gtk.TreePath(); @@ -168,7 +174,7 @@ public Gtk.TreeIter GetIterFromItem(TItem item, params int[] indices) return result; } - static Node GetNodeAtIter(Gtk.TreeIter iter) + public Node GetNodeAtIter(Gtk.TreeIter iter) { if (iter.UserData == IntPtr.Zero) return null; diff --git a/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs index cb9f73aae8..b3f2d04cb7 100755 --- a/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs @@ -105,8 +105,10 @@ public ITreeGridStore DataStore if (collection != null) collection.Unregister(); UnselectAll(); + suppressExpandCollapseEvents++; collection = new CollectionHandler { Handler = this }; collection.Register(value); + suppressExpandCollapseEvents--; EnsureSelection(); } } @@ -357,14 +359,14 @@ public override object GetItem(Gtk.TreePath path) public override int GetRowIndexOfPath(Gtk.TreePath path) { var tempPath = new Gtk.TreePath(); - int count = GetCount(Gtk.TreeIter.Zero, path.Indices[0]); + var item = DataStore; + int count = item.GetExpandedRowCount(path.Indices[0]); // slow but works for now for (int i = 0; i < path.Indices.Length - 1; i++) { - tempPath.AppendIndex(path.Indices[i]); - Gtk.TreeIter iter; - if (model.GetIter(out iter, tempPath)) - count += GetCount(iter, path.Indices[i + 1]); + item = item[path.Indices[i]] as ITreeGridStore; + if (item != null) + count += item.GetExpandedRowCount(path.Indices[i + 1]); } count += path.Indices.Length - 1; //count += path.Indices[row.Indices.Length - 1]; @@ -390,7 +392,7 @@ public override Gtk.TreePath GetPathAtRow(int row) { // Check path = Tree.Model.GetPath(iter); - if (GetRowIndexOfPath(path) == row) + if (model.GetRowIndexOfIter(iter) == row) return path; // Go Down @@ -426,58 +428,15 @@ public override Gtk.TreePath GetPathAtRow(int row) return Tree.Model.GetPath(iter); } - protected int GetRowCount() - { - Gtk.TreePath path; - Gtk.TreeIter iter; - Gtk.TreeIter temp; - - bool valid = Tree.Model.GetIterFirst(out iter); - int count = 0; - while (valid) - { - count++; - - // Go Down - path = Tree.Model.GetPath(iter); - if (Tree.GetRowExpanded(path) && Tree.Model.IterChildren(out iter, iter)) - continue; - - // Go Next - temp = iter; - if (Tree.Model.IterNext(ref iter)) - continue; - else - iter = temp; - - // Go Up and Next - while (true) - { - // Go Up - if (Tree.Model.IterParent(out iter, iter)) - { - // Go Next - temp = iter; - if (Tree.Model.IterNext(ref iter)) - break; - else - iter = temp; - } - else - return count; - } - } - return count; - } - protected override void SetSelectedRows(IEnumerable value) + { Tree.Selection.UnselectAll(); if (value != null && collection != null) { int start = -1; int end = -1; - var count = GetRowCount(); + var count = DataStore.GetExpandedRowCount(); foreach (var row in value.Where(r => r < count).OrderBy(r => r)) { @@ -506,15 +465,18 @@ protected override void SetSelectedRows(IEnumerable value) public GLib.Value GetColumnValue(ITreeGridItem item, int dataColumn, int row, Gtk.TreeIter iter) { + // yes, we can get the row.. but it slows down the TreeGridView too much when there are many items + // This is only used when formatting the cell, and all other platforms return row=-1 with TreeGridView if (dataColumn == RowDataColumn) - { - return new GLib.Value(GetRowIndexOfPath(model.GetPath(iter))); - } + return new GLib.Value(row); //model.GetRowIndexOfIter(iter)); + + if (dataColumn == ItemDataColumn) + return new GLib.Value(item); int column; if (ColumnMap.TryGetValue(dataColumn, out column)) { - var colHandler = (IGridColumnHandler)Widget.Columns[column].Handler; + var colHandler = (GridColumnHandler)Widget.Columns[column].Handler; return colHandler.GetValue(item, dataColumn, row); } return new GLib.Value((string)null); @@ -524,27 +486,24 @@ public int GetRowOfItem(ITreeGridItem item) { return collection == null ? -1 : collection.IndexOf(item); } - - int GetCount(Gtk.TreeIter parent, int upToIndex) + + (double? hscroll, double? vscroll) SaveScrollState() { - int rows = upToIndex == -1 ? model.IterNChildren(parent) : upToIndex; - int count = 0; - var path = model.GetPath(parent); - path.AppendIndex(0); - for (int i = 0; i < rows; i++) - { - Gtk.TreeIter iter; - if (Tree.GetRowExpanded(path)) - { - if (model.IterNthChild(out iter, parent, i)) - { - count += GetCount(iter, -1); - } - } - path.Next(); - count++; - } - return count; + var hscrollbar = Control.HScrollbar as Gtk.HScrollbar; + var vscrollbar = Control.VScrollbar as Gtk.VScrollbar; + var hscroll = hscrollbar?.Value; + var vscroll = vscrollbar?.Value; + return (hscroll, vscroll); + } + + void RestoreScrollState((double? hscroll, double? vscroll) state) + { + var hscrollbar = Control.HScrollbar as Gtk.HScrollbar; + var vscrollbar = Control.VScrollbar as Gtk.VScrollbar; + if (state.hscroll != null) + hscrollbar.Value = state.hscroll.Value; + if (state.vscroll != null) + vscrollbar.Value = state.vscroll.Value; } public void ReloadData() @@ -552,10 +511,7 @@ public void ReloadData() // save selected items var items = SelectedItems.ToArray(); // save scroll state - var hscrollbar = Control.HScrollbar as Gtk.HScrollbar; - var vscrollbar = Control.VScrollbar as Gtk.VScrollbar; - var hscroll = hscrollbar?.Value; - var vscroll = vscrollbar?.Value; + var scrollState = SaveScrollState(); // reload data and expand items suppressExpandCollapseEvents++; @@ -580,10 +536,7 @@ public void ReloadData() Callback.OnSelectionChanged(Widget, EventArgs.Empty); } SkipSelectedChange = false; - if (hscroll != null) - vscrollbar.Value = hscroll.Value; - if (vscroll != null) - vscrollbar.Value = vscroll.Value; + RestoreScrollState(scrollState); } public void ReloadItem(ITreeGridItem item, bool reloadChildren) diff --git a/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs b/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs index 3fc47d9e7e..f4f20e9f0f 100644 --- a/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs +++ b/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs @@ -151,11 +151,11 @@ private void ControlGotFocus(object sender, EventArgs e) public override void Layout() { base.Layout(); - var sv = this.Subviews; + var sv = Subviews; if (sv.Length > 0) { var view = sv[0]; - view.Frame = this.Bounds; + view.Frame = view.GetFrameForAlignmentRect(Bounds); } } diff --git a/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs b/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs index 7674b8ad2f..539395c499 100644 --- a/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs @@ -65,6 +65,9 @@ public interface IDataViewHandler void ResetAutoSizedColumns(); bool AutoSizeColumns(bool force, bool forceNewSize = false); + + int GetColumnDisplayIndex(GridColumn column); + void SetColumnDisplayIndex(GridColumn column, int index); } public interface IDataColumnHandler : GridColumn.IHandler @@ -75,10 +78,11 @@ public interface IDataColumnHandler : GridColumn.IHandler void SetObjectValue(object dataItem, NSObject val); new GridColumn Widget { get; } IDataViewHandler DataViewHandler { get; } - void AutoSizeColumn(NSRange rowRange, bool force = false); + void AutoSizeColumn(NSRange? rowRange, bool force = false); void EnabledChanged(bool value); nfloat GetPreferredWidth(NSRange? range = null); void SizeToFit(); + void SetupDisplayIndex(); } public class GridColumnHandler : MacObject, GridColumn.IHandler, IDataColumnHandler @@ -116,7 +120,7 @@ protected override void Initialize() base.Initialize(); } - public void AutoSizeColumn(NSRange rowRange, bool force = false) + public void AutoSizeColumn(NSRange? rowRange, bool force = false) { var handler = DataViewHandler; if (handler == null) @@ -139,10 +143,11 @@ public nfloat GetPreferredWidth(NSRange? range = null) return Width; var outlineView = handler.Table as NSOutlineView; + bool isOutlineColumn = outlineView != null && Column == 0; if (handler.ShowHeader) { width = (nfloat)Math.Max(Control.HeaderCell.CellSizeForBounds(new CGRect(0, 0, int.MaxValue, int.MaxValue)).Width, width); - if (outlineView != null && Column == 0) + if (isOutlineColumn) width += (float)outlineView.IndentationPerLevel; } @@ -154,6 +159,8 @@ public nfloat GetPreferredWidth(NSRange? range = null) var cellSize = Control.DataCell.CellSize; cellSize.Height = (nfloat)Math.Max(cellSize.Height, handler.RowHeight); var cell = DataCellHandler; + var displayIndex = DisplayIndex; + var columnRect = isOutlineColumn ? outlineView.RectForColumn(displayIndex) : CGRect.Empty; for (int i = (int)currentRange.Location; i < (int)(currentRange.Location + currentRange.Length); i++) { var item = DataViewHandler.GetItem(i); @@ -162,9 +169,10 @@ public nfloat GetPreferredWidth(NSRange? range = null) // -1 signifies that it doesn't support getting the preferred width if (cellWidth == -1) cellWidth = Control.Width; - else if (outlineView != null && Column == 0) + else if (isOutlineColumn) { - cellWidth += outlineView.GetCellFrame(0, i).X; + // gets the proper indent for the current row + cellWidth += outlineView.GetCellFrame(displayIndex, i).X - columnRect.X; } width = (nfloat)Math.Max(width, cellWidth); } @@ -418,6 +426,28 @@ public void EnabledChanged(bool value) } public void SizeToFit() => Control.SizeToFit(); + + + static readonly object DisplayIndex_Key = new object(); + public int DisplayIndex + { + get => DataViewHandler?.GetColumnDisplayIndex(Widget) ?? Widget.Properties.Get(DisplayIndex_Key) ?? -1; + set + { + Widget.Properties.Set(DisplayIndex_Key, value); + DataViewHandler?.SetColumnDisplayIndex(Widget, value); + } + } + + public void SetupDisplayIndex() + { + var displayIndex = Widget.Properties.Get(DisplayIndex_Key) ?? -1; + if (displayIndex >= 0) + { + DataViewHandler?.SetColumnDisplayIndex(Widget, displayIndex); + Widget.Properties.Remove(DisplayIndex_Key); + } + } } } diff --git a/src/Eto.Mac/Forms/Controls/GridHandler.cs b/src/Eto.Mac/Forms/Controls/GridHandler.cs index b9df5ea811..be3d270a6f 100644 --- a/src/Eto.Mac/Forms/Controls/GridHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridHandler.cs @@ -198,6 +198,10 @@ protected IEnumerable ColumnHandlers protected virtual void UpdateColumns() { + foreach (var col in ColumnHandlers) + { + col.SetupDisplayIndex(); + } } public GridColumnHandler GetColumn(NSTableColumn tableColumn) @@ -305,6 +309,8 @@ protected GridHandler() static void HandleScrolled(ObserverActionEventArgs e) { var handler = (GridHandler)e.Handler; + if (handler == null) + return; if (handler.hasAutoSizedColumns == true) handler.AutoSizeColumns(false); } @@ -332,10 +338,15 @@ protected override void Initialize() ScrollView.DocumentView = Control; } + public override void OnLoad(EventArgs e) + { + base.OnLoad(e); + UpdateColumns(); + } + public override void OnLoadComplete(EventArgs e) { base.OnLoadComplete(e); - UpdateColumns(); if (Widget.Columns.Any(r => r.Expand)) { @@ -356,8 +367,10 @@ public bool AutoSizeColumns(bool force, bool forceNewSize = false) if (Widget.Loaded) { var rect = Table.VisibleRect(); - var newRange = Table.RowsInRect(rect); - if (force || autoSizeRange.Location != newRange.Location || autoSizeRange.Length != newRange.Length) + var newRange = rect.IsEmpty ? null : (NSRange?)Table.RowsInRect(rect); + if (force + || newRange == null + || (autoSizeRange.Location != newRange.Value.Location || autoSizeRange.Length != newRange.Value.Length)) { IsAutoSizingColumns = true; int expandCount = 0; @@ -396,7 +409,8 @@ public bool AutoSizeColumns(bool force, bool forceNewSize = false) } } - autoSizeRange = newRange; + if (newRange != null) + autoSizeRange = newRange.Value; IsAutoSizingColumns = false; InvalidateMeasure(); return true; @@ -800,7 +814,23 @@ protected virtual bool ValidateProposedFirstResponder(NSResponder responder, NSE return false; } - + + public int GetColumnDisplayIndex(GridColumn column) + { + if (column.Handler is GridColumnHandler handler) + return (int)Control.FindColumn(new NSString(handler.Control.Identifier)); + return -1; + } + + public void SetColumnDisplayIndex(GridColumn column, int index) + { + if (column.Handler is GridColumnHandler handler) + { + var fromIndex = Control.FindColumn(new NSString(handler.Control.Identifier)); + if (fromIndex != index) + Control.MoveColumn(fromIndex, index); + } + } } } diff --git a/src/Eto.Mac/Forms/Controls/GridViewHandler.cs b/src/Eto.Mac/Forms/Controls/GridViewHandler.cs index 9746aa36ab..c78d638ab5 100644 --- a/src/Eto.Mac/Forms/Controls/GridViewHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridViewHandler.cs @@ -410,6 +410,15 @@ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn table return tableView.MakeView(tableColumn.Identifier, this); } + + public override void DidDragTableColumn(NSTableView tableView, NSTableColumn tableColumn) + { + var h = Handler; + if (h == null) + return; + var column = h.GetColumn(tableColumn); + h.Callback.OnColumnOrderChanged(h.Widget, new GridColumnEventArgs(column.Widget)); + } } public override void AttachEvent(string id) @@ -454,6 +463,9 @@ public override void AttachEvent(string id) case Eto.Forms.Control.DragDropEvent: // handled in EtoTableViewDataSource break; + case Grid.ColumnOrderChangedEvent: + // handled in EtoTableDelegate + break; default: base.AttachEvent(id); break; diff --git a/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs index e6c13f80e5..30bac2e688 100644 --- a/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs @@ -294,6 +294,15 @@ public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableCol } return outlineView.MakeView(tableColumn?.Identifier ?? string.Empty, this); } + + public override void DidDragTableColumn(NSOutlineView outlineView, NSTableColumn tableColumn) + { + var h = Handler; + if (h == null) + return; + var column = h.GetColumn(tableColumn); + h.Callback.OnColumnOrderChanged(h.Widget, new GridColumnEventArgs(column.Widget)); + } } public class EtoDataSource : NSOutlineViewDataSource @@ -704,6 +713,7 @@ public override void AttachEvent(string id) case TreeGridView.SelectedItemChangedEvent: case Grid.SelectionChangedEvent: case Grid.ColumnHeaderClickEvent: + case Grid.ColumnOrderChangedEvent: // handled in delegate break; case Grid.CellDoubleClickEvent: @@ -1043,7 +1053,10 @@ void SetItemExpansion(NSObject parent) public ITreeGridItem GetCellAt(PointF location, out int column) { location += ScrollView.ContentView.Bounds.Location.ToEto(); - column = (int)Control.GetColumn(location.ToNS()); + // this is the diplay index, we need the actual index + var displayColumnIndex = (int)Control.GetColumn(location.ToNS()); + var col = Widget.Columns.FirstOrDefault(r => r.DisplayIndex == displayColumnIndex); + column = col != null ? Widget.Columns.IndexOf(col) : -1; var row = Control.GetRow(location.ToNS()); if (row >= 0) { diff --git a/src/Eto.WinForms/Forms/Controls/GridColumnHandler.cs b/src/Eto.WinForms/Forms/Controls/GridColumnHandler.cs index 5929862ea0..6e632fd4ab 100644 --- a/src/Eto.WinForms/Forms/Controls/GridColumnHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/GridColumnHandler.cs @@ -161,6 +161,12 @@ public int MaxWidth } } + public int DisplayIndex + { + get => Control.DisplayIndex; + set => Control.DisplayIndex = value; + } + public void SetCellValue(object dataItem, object value) { if (dataCell != null) diff --git a/src/Eto.WinForms/Forms/Controls/GridHandler.cs b/src/Eto.WinForms/Forms/Controls/GridHandler.cs index 647e080d98..e1edb072cd 100644 --- a/src/Eto.WinForms/Forms/Controls/GridHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/GridHandler.cs @@ -338,12 +338,41 @@ public override void AttachEvent(string id) Callback.OnCellFormatting(Widget, new FormattingArgs(e, column, item, e.RowIndex)); }; break; + case Grid.ColumnOrderChangedEvent: + Control.ColumnDisplayIndexChanged += HandleColumnDisplayIndexChanged; + Control.MouseUp += HandleColumnOrderChangedOnMouseUp; + break; default: base.AttachEvent(id); break; } } + static readonly object ColumnOrderChanged_Key = new object(); + int? ColumnOrderChangedIndex + { + get => Widget.Properties.Get(ColumnOrderChanged_Key); + set => Widget.Properties.Set(ColumnOrderChanged_Key, value); + } + + private void HandleColumnOrderChangedOnMouseUp(object sender, swf.MouseEventArgs e) + { + if (ColumnOrderChangedIndex != null) + { + var column = Widget.Columns[ColumnOrderChangedIndex.Value]; + Callback.OnColumnOrderChanged(Widget, new GridColumnEventArgs(column)); + ColumnOrderChangedIndex = null; + } + } + + private void HandleColumnDisplayIndexChanged(object sender, swf.DataGridViewColumnEventArgs e) + { + // only store the first one (which is the one being dragged) + // we fire the event on the mouse up so it only happens once. + if (ColumnOrderChangedIndex == null) + ColumnOrderChangedIndex = e.Column.Index; + } + protected override void Initialize() { base.Initialize(); diff --git a/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs index 26ed8ec16c..3fb1d93b59 100644 --- a/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs @@ -27,7 +27,7 @@ public class TreeGridViewHandler : GridHandler= controller.Count) + if (row >= controller.Count || row < 0) return null; return controller[row]; } diff --git a/src/Eto.Wpf/Forms/Controls/GridColumnHandler.cs b/src/Eto.Wpf/Forms/Controls/GridColumnHandler.cs index dd0ae6bfd5..9d9d1a7919 100755 --- a/src/Eto.Wpf/Forms/Controls/GridColumnHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/GridColumnHandler.cs @@ -259,6 +259,12 @@ public int MaxWidth get => double.IsInfinity(Control.MaxWidth) ? int.MaxValue : (int)Control.MaxWidth; set => Control.MaxWidth = value == int.MaxValue ? double.PositiveInfinity : value; } + + public int DisplayIndex + { + get => Control.DisplayIndex; + set => Control.DisplayIndex = value; + } public void CellEdited(ICellHandler cell, sw.FrameworkElement element) { diff --git a/src/Eto.Wpf/Forms/Controls/GridHandler.cs b/src/Eto.Wpf/Forms/Controls/GridHandler.cs index 12a21b7780..fdce078070 100755 --- a/src/Eto.Wpf/Forms/Controls/GridHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/GridHandler.cs @@ -146,6 +146,7 @@ protected GridHandler() CanUserDeleteRows = false, CanUserResizeRows = false, CanUserAddRows = false, + CanUserReorderColumns = false, RowHeaderWidth = 0, SelectionMode = swc.DataGridSelectionMode.Single, GridLinesVisibility = swc.DataGridGridLinesVisibility.None, @@ -174,8 +175,8 @@ public override void AttachEvent(string id) { var row = e.Row.GetIndex(); var item = GetItemAtRow(row); - var gridColumn = Widget.Columns[e.Column.DisplayIndex]; - Callback.OnCellEditing(Widget, new GridViewCellEventArgs(gridColumn, row, e.Column.DisplayIndex, item)); + var gridColumn = GetColumn(e.Column); + Callback.OnCellEditing(Widget, new GridViewCellEventArgs(gridColumn, row, Widget.Columns.IndexOf(gridColumn), item)); }; break; case Grid.CellEditedEvent: @@ -212,12 +213,21 @@ public override void AttachEvent(string id) case Grid.CellFormattingEvent: // handled by FormatCell method break; + case Grid.ColumnOrderChangedEvent: + Control.ColumnReordered += HandleColumnReordered; + break; default: base.AttachEvent(id); break; } } + private void HandleColumnReordered(object sender, swc.DataGridColumnEventArgs e) + { + var column = GetColumn(e.Column); + Callback.OnColumnOrderChanged(Widget, new GridColumnEventArgs(column)); + } + GridCellMouseEventArgs CreateCellMouseArgs(object originalSource, swi.MouseButtonEventArgs ea) => CreateCellMouseArgs(originalSource, ea, out _); GridCellMouseEventArgs CreateCellMouseArgs(object originalSource, swi.MouseButtonEventArgs ea, out bool isValid) @@ -226,10 +236,10 @@ GridCellMouseEventArgs CreateCellMouseArgs(object originalSource, swi.MouseButto var row = GetRowOfElement(originalSource, out cell, out isValid); int rowIndex = row?.GetIndex() ?? -1; - var columnIndex = cell?.Column?.DisplayIndex ?? -1; + var column = GetColumn(cell?.Column); + var columnIndex = column != null ? Widget.Columns.IndexOf(column) : -1; var item = row?.Item; - var column = columnIndex == -1 || columnIndex >= Widget.Columns.Count ? null : Widget.Columns[columnIndex]; var buttons = ea.GetEtoButtons(); var modifiers = swi.Keyboard.Modifiers.ToEto(); @@ -822,12 +832,26 @@ protected void RestoreFocus() RestoreColumnFocus(); } } + + GridColumn GetColumn(swc.DataGridColumn dataGridColumn) + { + if (dataGridColumn == null) + return null; + var columns = Widget.Columns; + foreach (var col in columns) + { + if (col.Handler is IGridColumnHandler handler && ReferenceEquals(handler.Control, dataGridColumn)) + return col; + } + return null; + } public void CellEdited(int row, swc.DataGridColumn dataGridColumn, object dataItem) { - var gridColumn = Widget.Columns[dataGridColumn.DisplayIndex]; + var gridColumn = GetColumn(dataGridColumn); + var columnIndex = gridColumn != null ? Widget.Columns.IndexOf(gridColumn) : -1; SetIsEditing(false); - Callback.OnCellEdited(Widget, new GridViewCellEventArgs(gridColumn, row, dataGridColumn.DisplayIndex, dataItem)); + Callback.OnCellEdited(Widget, new GridViewCellEventArgs(gridColumn, row, columnIndex, dataItem)); SetIsEditing(null); } diff --git a/src/Eto/Forms/Controls/Grid.cs b/src/Eto/Forms/Controls/Grid.cs index f309403aca..d38db423fc 100644 --- a/src/Eto/Forms/Controls/Grid.cs +++ b/src/Eto/Forms/Controls/Grid.cs @@ -360,6 +360,29 @@ public event EventHandler SelectedRowsChanged add { SelectionChanged += value; } remove { SelectionChanged -= value; } } + + /// + /// Event identifier for handlers when attaching the event + /// + public const string ColumnOrderChangedEvent = "Grid.ColumnOrderChanged"; + + /// + /// Event to handle when a column has been reordered by the user. + /// + public event EventHandler ColumnOrderChanged + { + add { Properties.AddHandlerEvent(ColumnOrderChangedEvent, value); } + remove { Properties.RemoveEvent(ColumnOrderChangedEvent, value); } + } + + /// + /// Raises the event + /// + /// Event arguments + protected virtual void OnColumnOrderChanged(GridColumnEventArgs e) + { + Properties.TriggerEvent(ColumnOrderChangedEvent, this, e); + } #endregion @@ -372,6 +395,7 @@ static Grid() EventLookup.Register(c => c.OnCellDoubleClick(null), Grid.CellDoubleClickEvent); EventLookup.Register(c => c.OnSelectionChanged(null), Grid.SelectionChangedEvent); EventLookup.Register(c => c.OnColumnHeaderClick(null), Grid.ColumnHeaderClickEvent); + EventLookup.Register(c => c.OnColumnOrderChanged(null), Grid.ColumnOrderChangedEvent); } /// @@ -666,6 +690,11 @@ protected override object GetCallback() /// Raises the cell formatting event. /// void OnCellFormatting(Grid widget, GridCellFormatEventArgs e); + + /// + /// Raises the column display index changed event. + /// + void OnColumnOrderChanged(Grid widget, GridColumnEventArgs e); } /// @@ -738,6 +767,15 @@ public void OnCellFormatting(Grid widget, GridCellFormatEventArgs e) using (widget.Platform.Context) widget.OnCellFormatting(e); } + + /// + /// Raises the column display index changed event. + /// + public void OnColumnOrderChanged(Grid widget, GridColumnEventArgs e) + { + using (widget.Platform.Context) + widget.OnColumnOrderChanged(e); + } } #region Handler diff --git a/src/Eto/Forms/Controls/GridColumn.cs b/src/Eto/Forms/Controls/GridColumn.cs index 8027cbad6a..0bda02e5f0 100644 --- a/src/Eto/Forms/Controls/GridColumn.cs +++ b/src/Eto/Forms/Controls/GridColumn.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.ComponentModel; namespace Eto.Forms { @@ -17,7 +18,7 @@ public class GridColumnCollection : ObservableCollection [ContentProperty("DataCell")] public class GridColumn : Widget { - new IHandler Handler { get { return (IHandler)base.Handler; } } + new IHandler Handler => (IHandler)base.Handler; /// /// Gets or sets the text to display in the header of the column. @@ -145,6 +146,19 @@ public int MaxWidth get => Handler.MaxWidth; set => Handler.MaxWidth = value; } + + /// + /// Gets or sets the index of the column in display order, or -1 to use the order they are added. + /// + /// + /// This value must be within the range of the total number of columns that are added. + /// + [DefaultValue(-1)] + public int DisplayIndex + { + get => Handler.DisplayIndex; + set => Handler.DisplayIndex = value; + } /// /// Handler interface for the . @@ -229,6 +243,14 @@ public int MaxWidth /// /// The column maximum width int MaxWidth { get; set; } + + /// + /// Gets or sets the index of the column in display order, or -1 to use the order they are added. + /// + /// + /// This value must be within the range of the total number of columns that are added. + /// + int DisplayIndex { get; set; } } } } diff --git a/src/Eto/Forms/Controls/TreeGridStore.cs b/src/Eto/Forms/Controls/TreeGridStore.cs new file mode 100644 index 0000000000..f5ccdc139e --- /dev/null +++ b/src/Eto/Forms/Controls/TreeGridStore.cs @@ -0,0 +1,74 @@ +using System; + +namespace Eto.Forms +{ + /// + /// Item store for the + /// + public interface ITreeGridStore : IDataStore + where T: ITreeGridItem + { + } + + /// + /// Extension methods for the + /// + public static class TreeGridStoreExtensions + { + /// + /// Gets the expanded row count of the specified ITreeGridStore, taking into account the expanded state of each child. + /// + /// + /// Note that this has to traverse the entire tree up to the , so it will get slower for large trees + /// + /// Type of item + /// The data store to count the rows + /// The index in to count up to, or -1 to count all rows + /// The total row count including the count of any expanded nodes + /// When store is null + public static int GetExpandedRowCount(this IDataStore store, int index = -1) + where T: ITreeItem + { + if (store == null) throw new ArgumentNullException(nameof(store)); + + int rows = index == -1 ? store.Count : index; + int count = rows; + + for (int i = 0; i < rows; i++) + { + var child = store[i]; + if (child.Expanded && child is IDataStore childStore) + { + count += GetExpandedRowCount(childStore, -1); + } + } + return count; + } + + /// + /// Gets the row of a path of indecies for each level. + /// + /// Type of item + /// The data store to get the row. + /// Array of indexes leading to the item to get the row for. + /// Row index where the specified index path points to + /// When store is null + public static int GetRowOfIndexPath(this IDataStore store, int[] indexPath) + where T: ITreeItem + { + if (indexPath == null || indexPath.Length == 0) + return -1; + + var item = store ?? throw new ArgumentNullException(nameof(store)); + int count = item.GetExpandedRowCount(indexPath[0]); + for (int i = 0; i < indexPath.Length - 1; i++) + { + item = item[indexPath[i]] as IDataStore; + if (item != null) + count += item.GetExpandedRowCount(indexPath[i + 1]); + } + count += indexPath.Length - 1; + return count; + } + } +} diff --git a/test/Eto.Test/MainForm.cs b/test/Eto.Test/MainForm.cs index 9f3dfe11db..987104f9c0 100644 --- a/test/Eto.Test/MainForm.cs +++ b/test/Eto.Test/MainForm.cs @@ -51,7 +51,7 @@ public MainForm(IEnumerable
topNodes = null) Title = $"Test Application [{Platform.ID}, {bitness}, {runtime}, {platform}]"; Style = "main"; - MinimumSize = new Size(400, 400); + MinimumSize = new Size(600, 500); topNodes = topNodes ?? TestSections.Get(TestApplication.DefaultTestAssemblies()); var nodes = topNodes.ToList(); @@ -66,7 +66,7 @@ public MainForm(IEnumerable
topNodes = null) this.Icon = TestIcons.TestIcon; if (Platform.IsDesktop) - ClientSize = new Size(900, 650); + ClientSize = new Size(1024, 700); //Opacity = 0.5; Content = MainContent(); diff --git a/test/Eto.Test/Sections/Controls/GridViewSection.cs b/test/Eto.Test/Sections/Controls/GridViewSection.cs index 17ac5af823..726e8942f2 100644 --- a/test/Eto.Test/Sections/Controls/GridViewSection.cs +++ b/test/Eto.Test/Sections/Controls/GridViewSection.cs @@ -10,102 +10,186 @@ namespace Eto.Test.Sections.Controls { - class LogGridItem : GridItem + [Section("Controls", typeof(GridView))] + public class GridViewSection : GridViewSection { - public int Row { get; set; } + protected SelectableFilterCollection filteredCollection; - public LogGridItem(params object[] values) - : base(values) + protected override string GetCellInfo(GridView grid, PointF location) { + var cell = grid.GetCellAt(location); + return $"Row: {cell?.RowIndex}, Column: {cell?.ColumnIndex} ({cell?.Column?.HeaderText}), Item: {cell?.Item}"; } - public override void SetValue(int column, object value) + protected override int GetRowCount(GridView grid) => ((ICollection)grid.DataStore).Count; + + protected override void ReloadData(GridView grid) => grid.ReloadData(grid.SelectedRows); + + protected override void SetContextMenu(GridView grid, ContextMenu menu) => grid.ContextMenu = menu; + + protected override void SetDataStore(GridView grid) { - base.SetValue(column, value); - Log.Write(this, "SetValue, Row: {0}, Column: {1}, Value: {2}", Row, column, value); + filteredCollection = new SelectableFilterCollection(grid, CreateItems()); + grid.DataStore = filteredCollection; + } + + List CreateItems(int count = 10000) + { + var list = new List(count); + + for (int i = 0; i < count; i++) + { + list.Add(new MyGridItem(i, $"Row {i}")); + } + return list; + } + + SearchBox CreateSearchBox() + { + var filterText = new SearchBox { PlaceholderText = "Filter" }; + filterText.TextChanged += (s, e) => + { + var filterItems = (filterText.Text ?? "").Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + if (filterItems.Length == 0) + filteredCollection.Filter = null; + else + filteredCollection.Filter = i => + { + // Every item in the split filter string should be within the Text property + for (int i1 = 0; i1 < filterItems.Length; i1++) + { + string filterItem = filterItems[i1]; + if (i.Text.IndexOf(filterItem, StringComparison.OrdinalIgnoreCase) == -1) + { + return false; + } + } + return true; + }; + }; + return filterText; + } + + Button AddItemButton() + { + var control = new Button { Text = "Add Item" }; + control.Click += (sender, e) => filteredCollection.Add(new MyGridItem(filteredCollection.Count + 1, $"New Row {filteredCollection.Count + 1}")); + return control; + } + + protected override void AddExtraControls(DynamicLayout layout) + { + layout.AddSeparateRow(null, AddItemButton(), null); + layout.AddSeparateRow(CreateSearchBox()); + base.AddExtraControls(layout); + } + + protected override void AddExtraContextMenuItems(ContextMenu menu) + { + // Delete menu item: deletes the item from the store, the UI updates via the binding. + var deleteItem = new ButtonMenuItem { Text = "Delete Item" }; + deleteItem.Click += (s, e) => + { + var i = grid.SelectedItems.First() as MyGridItem; + if (i != null) + filteredCollection.Remove(i); + }; + menu.Items.Add(deleteItem); + + // Insert item: inserts an item into the store, the UI updates via the binding. + var insertItem = new ButtonMenuItem { Text = "Insert Item" }; + insertItem.Click += (s, e) => + { + var i = grid.SelectedItems.First() as MyGridItem; + if (i != null) + { + filteredCollection.Insert(filteredCollection.IndexOf(i), new MyGridItem(0, "")); + } + }; + menu.Items.Add(insertItem); + base.AddExtraContextMenuItems(menu); + } + + protected override int GetColumnAt(PointF location) + { + return grid.GetCellAt(location).ColumnIndex; } } - [Section("Controls", typeof(GridView))] - public class GridViewSection : Panel + public abstract class GridViewSection : Panel + where T : Grid, new() { static Icon image1 = new Icon(1, TestIcons.TestImage).WithSize(16, 16); static Icon image2 = TestIcons.TestIcon.WithSize(16, 16); - GridView gridView; - SelectableFilterCollection filteredCollection; + protected T grid; + + protected abstract void SetContextMenu(T grid, ContextMenu menu); + protected abstract int GetRowCount(T grid); + protected abstract void SetDataStore(T grid); + protected abstract void ReloadData(T grid); + protected abstract string GetCellInfo(T grid, PointF location); + protected abstract int GetColumnAt(PointF location); public GridViewSection() { Styles.Add