diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f07801c684..f50700f45c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,6 +12,9 @@ "group": { "kind": "build", "isDefault": true + }, + "presentation": { + "clear": true } }, { @@ -23,41 +26,59 @@ "type": "shell", "problemMatcher": "$msCompile", "group": "build", + "presentation": { + "clear": true + } }, { "label": "build-gtk", "command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.Gtk/Eto.Test.Gtk.csproj", "type": "shell", "group": "build", - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "presentation": { + "clear": true + } }, { "label": "build-mac64", "command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.Mac/Eto.Test.Mac64.csproj", "type": "shell", "group": "build", - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "presentation": { + "clear": true + } }, { "label": "build-xammac2", "command": "msbuild /restore ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.Mac/Eto.Test.XamMac2.csproj", "type": "shell", "group": "build", - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "presentation": { + "clear": true + } }, { "label": "build-wpf", "command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj", "type": "shell", "group": "build", - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "presentation": { + "clear": true + } }, { "label": "build-winforms", "command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.WinForms/Eto.Test.WinForms.csproj", "type": "shell", "group": "build", - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "presentation": { + "clear": true + } }, { "label": "restore", @@ -72,7 +93,10 @@ "windows": { "command": "IF ('${input:confirm}' -ne 'Yes') { exit 1 }; git clean -dxff ; git submodule foreach git clean -dxff ; git submodule update --init --recursive" }, - "problemMatcher": [] + "problemMatcher": [], + "presentation": { + "clear": true + } }, ], "inputs": [ diff --git a/build/Build.proj b/build/Build.proj index 86dc9ff0f9..c514e787e3 100644 --- a/build/Build.proj +++ b/build/Build.proj @@ -154,7 +154,7 @@ - + Configuration=$(Configuration);Platform=$(OSPlatform) diff --git a/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs b/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs index 8f902e5cfb..25b92236bb 100644 --- a/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs +++ b/src/Eto.Gtk/Forms/Cells/CustomCellHandler.cs @@ -106,7 +106,8 @@ public int Row IGtkCellEditable CreateEditable(Gdk.Rectangle cellArea) { var item = Handler.Source.GetItem(Row); - var args = new CellEventArgs(null, Handler.Widget, Row, item, CellStates.Editing); + int column = -1; + var args = new CellEventArgs(null, Handler.Widget, Row, column, item, CellStates.Editing, null); var ed = new EtoCellEditable(); ed.Content = Handler.Callback.OnCreateCell(Handler.Widget, args); @@ -125,7 +126,10 @@ IGtkCellEditable CreateEditable(Gdk.Rectangle cellArea) 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) { base.GetSize(widget, ref cell_area, out x_offset, out y_offset, out width, out height); - height = Math.Max(height, Handler.Source.RowHeight); + var h = Handler; + if (h == null) + return; + height = Math.Max(height, h.Source.RowHeight); } public override Gtk.CellEditable StartEditing(Gdk.Event evnt, Gtk.Widget widget, string path, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) @@ -135,22 +139,42 @@ public override Gtk.CellEditable StartEditing(Gdk.Event evnt, Gtk.Widget widget, protected override void Render(Gdk.Drawable window, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, Gtk.CellRendererState flags) { + var h = Handler; + if (h == null) + return; + if (editingRow == row) { return; } using (var graphics = new Graphics(new GraphicsHandler(widget, window))) { - var item = Handler.Source.GetItem(Row); + var item = h.Source.GetItem(Row); var args = new CellPaintEventArgs(graphics, cell_area.ToEto(), flags.ToEto(), item); - Handler.Callback.OnPaint(Handler.Widget, args); + h.Callback.OnPaint(h.Widget, args); } } #else protected override void OnGetPreferredHeight(Gtk.Widget widget, out int minimum_size, out int natural_size) { base.OnGetPreferredHeight(widget, out minimum_size, out natural_size); - natural_size = Handler.Source.RowHeight; + var h = Handler; + if (h == null) + return; + natural_size = h.Source.RowHeight; + } + + protected override void OnGetPreferredWidth(Gtk.Widget widget, out int minimum_size, out int natural_size) + { + base.OnGetPreferredWidth(widget, out minimum_size, out natural_size); + var h = Handler; + if (h == null) + return; + var item = h.Source?.GetItem(Row); + int column = -1; + var args = new CellEventArgs(null, h.Widget, Row, column, item, CellStates.Editing, null); + + natural_size = (int)h.Callback.OnGetPreferredWidth(h.Widget, args); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs b/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs index 60d9464201..d9a27be0a7 100644 --- a/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs +++ b/src/Eto.Mac/Forms/Cells/CustomCellHandler.cs @@ -52,7 +52,8 @@ public override NSObject GetObjectValue(object dataItem) public override nfloat GetPreferredWidth(object value, CGSize cellSize, int row, object dataItem) { - var args = new CellEventArgs(ColumnHandler?.DataViewHandler as Grid, Widget, row, dataItem, CellStates.None); + var column = -1;// TODO: lookup! + var args = new MutableCellEventArgs(ColumnHandler?.DataViewHandler as Grid, Widget, row, column, dataItem, CellStates.None, null); var identifier = Callback.OnGetIdentifier(Widget, args) ?? string.Empty; Control widthCell; if (!widthCells.TryGetValue(identifier, out widthCell)) @@ -63,6 +64,7 @@ public override nfloat GetPreferredWidth(object value, CGSize cellSize, int row, widthCell.AttachNative(); widthCells.Add(identifier, widthCell); } + args.SetControl(widthCell); Callback.OnConfigureCell(Widget, args, widthCell); widthCell.GetMacControl()?.InvalidateMeasure(); @@ -76,6 +78,7 @@ public override nfloat GetPreferredWidth(object value, CGSize cellSize, int row, public class EtoCustomCellView : NSTableCellView { + public CustomCellHandler Handler { get; set;} public MutableCellEventArgs Args { get; set; } public Control EtoControl { get; set; } @@ -109,6 +112,43 @@ public override NSBackgroundStyle BackgroundStyle Args.SetTextColor(ControlText); } } + + public void Setup() + { + EtoControl.GotFocus += ControlGotFocus; + EtoControl.LostFocus += ControlLostFocus; + } + + bool losingFocus; + + private void ControlLostFocus(object sender, EventArgs e) + { + if (losingFocus) + return; + + losingFocus = true; + var h = Handler; + var ee = MacConversions.CreateCellEventArgs(h.ColumnHandler.Widget, null, Args.Row, Args.Column, Args.Item); + if (!h.ColumnHandler.DataViewHandler.IsCancellingEdit) + { + h.Callback.OnCommitEdit(h.Widget, Args); + h.ColumnHandler.DataViewHandler.OnCellEdited(ee); + } + else + { + h.Callback.OnCancelEdit(h.Widget, Args); + } + losingFocus = false; + } + + private void ControlGotFocus(object sender, EventArgs e) + { + var h = Handler; + h.Callback.OnBeginEdit(h.Widget, Args); + var ee = MacConversions.CreateCellEventArgs(h.ColumnHandler.Widget, null, Args.Row, Args.Column, Args.Item); + h.ColumnHandler.DataViewHandler.OnCellEditing(ee); + } + } public override Color GetBackgroundColor(NSView view) @@ -131,7 +171,8 @@ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn table state |= CellStates.Selected; if (tableColumn.Editable) state |= CellStates.Editing; - var args = new MutableCellEventArgs(ColumnHandler.DataViewHandler as Grid, Widget, row, item, state); + var column = -1; // TODO: get index or lookup when needed. + var args = new MutableCellEventArgs(ColumnHandler.DataViewHandler as Grid, Widget, row, column, item, state, null); var identifier = tableColumn.Identifier; var id = Callback.OnGetIdentifier(Widget, args); if (!string.IsNullOrEmpty(id)) @@ -141,9 +182,11 @@ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn table if (view == null) { var control = Callback.OnCreateCell(Widget, args); + args.SetControl(control); view = new EtoCustomCellView { + Handler = this, Args = args, EtoControl = control, Identifier = identifier, @@ -156,6 +199,7 @@ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn table childView.AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable; childView.Frame = view.Frame; view.AddSubview(childView); + view.Setup(); } } else diff --git a/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs b/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs index 360c1247f1..6e86301e1e 100644 --- a/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridColumnHandler.cs @@ -60,6 +60,8 @@ public interface IDataViewHandler Grid Widget { get; } bool SuppressUpdate { get; } + + bool IsCancellingEdit { get; } } public interface IDataColumnHandler diff --git a/src/Eto.Mac/Forms/Controls/GridHandler.cs b/src/Eto.Mac/Forms/Controls/GridHandler.cs index 3e7be8b08e..fdaaf68dc7 100644 --- a/src/Eto.Mac/Forms/Controls/GridHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridHandler.cs @@ -85,6 +85,7 @@ static class GridHandler public static readonly object IsEditing_Key = new object(); public static readonly object IsMouseDragging_Key = new object(); public static readonly object ContextMenu_Key = new object(); + public static readonly object IsCancelEdit_Key = new object(); } class EtoTableHeaderView : NSTableHeaderView @@ -474,7 +475,10 @@ public void UnselectAll() public void BeginEdit(int row, int column) { - Control.SelectRow((nnint)row, false); + if (!Control.IsRowSelected(row)) + { + Control.SelectRow((nnint)row, false); + } Control.EditColumn((nint)column, (nint)row, new NSEvent(), true); } @@ -482,10 +486,16 @@ public void BeginEdit(int row, int column) public bool CancelEdit() { - SuppressUpdate++; - var ret = SetFocusToControl(); - SuppressUpdate--; - return ret; + if (IsEditing) + { + SuppressUpdate++; + IsCancellingEdit = true; + var ret = SetFocusToControl(); + IsCancellingEdit = false; + SuppressUpdate--; + return ret; + } + return true; } bool SetFocusToControl() @@ -588,10 +598,16 @@ void IDataViewHandler.OnCellEditing(GridViewCellEventArgs e) SetIsEditing(true); } + public bool IsCancellingEdit + { + get => Widget.Properties.Get(GridHandler.IsCancelEdit_Key); + set => Widget.Properties.Set(GridHandler.IsCancelEdit_Key, value); + } + void IDataViewHandler.OnCellEdited(GridViewCellEventArgs e) { SetIsEditing(false); - if (e.Item != null) + if (e.Item != null && !IsCancellingEdit) Callback.OnCellEdited(Widget, e); // reload this entire row diff --git a/src/Eto.Wpf/Forms/Cells/CustomCellHandler.cs b/src/Eto.Wpf/Forms/Cells/CustomCellHandler.cs old mode 100644 new mode 100755 index 8def2203f3..f6b4ed3659 --- a/src/Eto.Wpf/Forms/Cells/CustomCellHandler.cs +++ b/src/Eto.Wpf/Forms/Cells/CustomCellHandler.cs @@ -3,10 +3,14 @@ using sw = System.Windows; using swd = System.Windows.Data; using swm = System.Windows.Media; +using swi = System.Windows.Input; using Eto.Wpf.Drawing; using Eto.Drawing; using System.Collections.Generic; using System; +using System.Windows; +using System.Windows.Input; +using Eto.Wpf.Forms.Controls; namespace Eto.Wpf.Forms.Cells { @@ -21,18 +25,46 @@ object GetValue(object dataItem) public class WpfCellEventArgs : CellEventArgs { - public WpfCellEventArgs(Grid grid, CustomCell cell, int row, object item, CellStates cellState) - : base(grid, cell, row, item, cellState) + swc.DataGridColumn _gridColumn; + int? _column; + public WpfCellEventArgs(Grid grid, CustomCell cell, int row, swc.DataGridColumn column, object item, CellStates cellState, Control control = null) + : base(grid, cell, row, -1, item, cellState, control) { + _gridColumn = column; + } + + public override int Column => _column ?? (_column = GetColumnIndex()).Value; + + int GetColumnIndex() + { + if (_gridColumn != null && Grid.ControlObject is swc.DataGrid grid) + { + return grid.Columns.IndexOf(_gridColumn); + } + return -1; } public void SetSelected(swc.DataGridCell cell) { - var row = cell.GetVisualParent(); + var grid = cell.GetVisualParent(); var selected = cell.IsSelected; IsSelected = selected; - var focused = row?.IsKeyboardFocusWithin == true; - CellTextColor = selected && focused ? SystemColors.HighlightText : SystemColors.ControlText; + var focused = grid?.IsKeyboardFocusWithin == true; + CellTextColor = selected ? Eto.Drawing.SystemColors.HighlightText : Eto.Drawing.SystemColors.ControlText; + } + public void SetRow(sw.FrameworkElement element) + { + SetRow(element.GetVisualParent()?.GetIndex() ?? -1); + } + + public void SetRow(int row) + { + Row = row; + } + + public void SetIsEditing(bool isEditing) + { + IsEditing = isEditing; } public void SetDataContext(object dataContext) @@ -84,6 +116,9 @@ EtoBorder Create(swc.DataGridCell cell) control.Unloaded += HandleControlUnloaded; control.Loaded += HandleControlLoaded; control.DataContextChanged += HandleControlDataContextChanged; + control.PreviewMouseDown += HandlePreviewMouseDown; + control.IsKeyboardFocusWithinChanged += HandleIsKeyboardFocusWithinChanged; + if (!Equals(cell.GetValue(dpSelectedHookedUp), true)) { cell.SetValue(dpSelectedHookedUp, true); @@ -100,6 +135,44 @@ EtoBorder Create(swc.DataGridCell cell) return control; } + private void HandlePreviewMouseDown(object sender, MouseButtonEventArgs e) + { + var ctl = sender as sw.FrameworkElement; + var cell = ctl?.GetVisualParent(); + if (!cell.IsKeyboardFocusWithin) + { + cell.IsEditing = true; + //var row = cell.GetVisualParent(); + //if (row != null) + // row.IsSelected = true; + //var ee = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice); + //ee.RoutedEvent = sw.FrameworkElement.MouseDownEvent; + //cell.RaiseEvent(ee); + //e.Handled = true; + } + } + + void HandleIsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var h = Handler; + var ctl = sender as sw.FrameworkElement; + var cell = ctl?.GetVisualParent(); + var isEditing = ctl.IsKeyboardFocusWithin || cell.IsEditing; + var args = GetEditArgs(cell, ctl); + if (args?.IsEditing != isEditing) + { + args.SetIsEditing(isEditing); + //cell.IsEditing = isEditing; + if (isEditing) + { + h.Callback.OnBeginEdit(h.Widget, args); + } + else { + h.Callback.OnCommitEdit(h.Widget, args); + h.ContainerHandler.CellEdited(h, ctl); + } + } + } static void HandleRowFocusChanged(object sender, sw.DependencyPropertyChangedEventArgs e) { var grid = sender as swc.DataGrid; @@ -127,8 +200,9 @@ static void HandleControlDataContextChanged(object sender, sw.DependencyProperty var handler = col?.Handler; if (handler == null) return; - var args = new WpfCellEventArgs(handler.ContainerHandler?.Grid, handler.Widget, -1, ctl.DataContext, CellStates.None); + var args = new WpfCellEventArgs(handler.ContainerHandler?.Grid, handler.Widget, -1, cell.Column, ctl.DataContext, CellStates.None); args.SetSelected(cell); + args.SetRow(cell); var id = handler.Callback.OnGetIdentifier(handler.Widget, args); var child = ctl.Control; if (id != ctl.Identifier || child == null) @@ -231,6 +305,82 @@ protected override sw.FrameworkElement GenerateEditingElement(swc.DataGridCell c { return Handler.SetupCell(Create(cell)); } + + protected override object PrepareCellForEdit(sw.FrameworkElement editingElement, sw.RoutedEventArgs editingEventArgs) + { + var obj = base.PrepareCellForEdit(editingElement, editingEventArgs); + var handler = Handler; + var cell = editingElement?.GetParent(); + var args = GetEditArgs(cell, editingElement); + if (handler != null && args != null) + { + args.SetIsEditing(true); + handler.Callback.OnBeginEdit(handler.Widget, args); + } + if (args?.Handled != true && !cell.IsKeyboardFocusWithin) + { + // default implementation is to focus the cell's control + editingElement.Focus(); + editingElement.MoveFocus(new swi.TraversalRequest(swi.FocusNavigationDirection.First)); + } + return obj; + } + + WpfCellEventArgs GetEditArgs(swc.DataGridCell cell, FrameworkElement editingElement) + { + var handler = Handler; + if (handler == null) + return null; + var ctl = editingElement as EtoBorder; + var args = ctl.Control.Properties.Get(CellEventArgs_Key); + if (args == null) + { + args = new WpfCellEventArgs(handler.ContainerHandler?.Grid, handler.Widget, -1, cell.Column, ctl.DataContext, CellStates.None, ctl.Control); + ctl.Control.Properties.Set(CellEventArgs_Key, args); + } + args.Handled = false; + return args; + } + + protected override bool CommitCellEdit(sw.FrameworkElement editingElement) + { + var result = base.CommitCellEdit(editingElement); + var handler = Handler; + var cell = editingElement?.GetParent(); + var args = GetEditArgs(cell, editingElement); + if (handler != null && args != null) + { + args.SetIsEditing(false); + cell.IsEditing = false; + handler.Callback.OnCommitEdit(handler.Widget, args); + } + if (args?.Handled != true && editingElement.IsKeyboardFocusWithin) + { + // default implementation is to move focus back to the grid + var grid = editingElement.GetVisualParent(); + grid?.Focus(); + } + handler?.ContainerHandler.CellEdited(handler, editingElement); + + return true; + } + + protected override void CancelCellEdit(sw.FrameworkElement editingElement, object uneditedValue) + { + var handler = Handler; + var cell = editingElement?.GetParent(); + var args = GetEditArgs(cell, editingElement); + if (handler != null && args != null) + { + args.SetIsEditing(false); + cell.IsEditing = false; + handler.Callback.OnCancelEdit(handler.Widget, args); + } + if (args?.Handled != true && editingElement.IsKeyboardFocusWithin) + { + editingElement.GetVisualParent()?.Focus(); + } + } } public CustomCellHandler() diff --git a/src/Eto.Wpf/Forms/Controls/GridHandler.cs b/src/Eto.Wpf/Forms/Controls/GridHandler.cs index a321d84804..b560b781f6 100755 --- a/src/Eto.Wpf/Forms/Controls/GridHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/GridHandler.cs @@ -581,10 +581,15 @@ public void UnselectAll() public void BeginEdit(int row, int column) { - Control.UnselectAll(); + CommitEdit(); //sometimes couldn't focus to cell, so use ScrollIntoView Control.ScrollIntoView(Control.Items[row]); - //set current cell + //set current cell and select its row. + if (!SelectedRows.Contains(row)) + { + Control.UnselectAll(); + Control.SelectedIndex = row; + } Control.CurrentCell = new swc.DataGridCellInfo(Control.Items[row], Control.Columns[column]); Control.Focus(); Control.BeginEdit(); diff --git a/src/Eto/Forms/Cells/CustomCell.cs b/src/Eto/Forms/Cells/CustomCell.cs old mode 100644 new mode 100755 index b192d846a5..b03454c675 --- a/src/Eto/Forms/Cells/CustomCell.cs +++ b/src/Eto/Forms/Cells/CustomCell.cs @@ -79,7 +79,7 @@ protected set /// Gets or sets the row for the cell. /// /// The cell's row. - public int Row + public virtual int Row { get { return row; } protected set @@ -92,6 +92,18 @@ protected set } } + /// + /// Gets the column index for the cell. + /// + /// The cell's column index. + public virtual int Column { get; } + + /// + /// Gets the column for the cell. + /// + /// The cell's column. + public GridColumn GridColumn => Column >= 0 ? Grid?.Columns[Column] : null; + /// /// Gets the cell that triggered this event /// @@ -102,6 +114,21 @@ protected set /// public Grid Grid { get; } + /// + /// Gets the custom control associated with the cell (if any) + /// + /// Instance of the control for the cell + public Control Control { get; protected set; } + + /// + /// Gets or sets a value indicating that the default behavior should not be executed for the event, if supported. + /// + /// + /// Note that not all events can be handled. + /// + /// True if the event is user-handled, false to use system behavior. + public bool Handled { get; set; } + Color cellTextColor = SystemColors.ControlText; /// @@ -141,13 +168,31 @@ public CellEventArgs(int row, object item, CellStates cellState) /// Row for the cell. /// Item the cell is displaying. /// State of the cell. + [Obsolete("Use constructor with column number and control")] public CellEventArgs(Grid grid, Cell cell, int row, object item, CellStates cellState) + : this(null, null, row, -1, item, cellState, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Grid the event is triggered for. + /// Cell the event is triggered for. + /// Row for the cell. + /// Column for the cell. + /// Item the cell is displaying. + /// State of the cell. + /// Control object for the cell (if any) + public CellEventArgs(Grid grid, Cell cell, int row, int column, object item, CellStates cellState, Control control) { Grid = grid; Cell = cell; + Column = column; Row = row; Item = item; CellState = cellState; + Control = control; } /// @@ -359,6 +404,37 @@ protected virtual void OnConfigureCell(CellEventArgs args, Control control) control.DataContext = args.Item; } + /// + /// Event to handle when the cell should begin editing. + /// + public event EventHandler BeginEdit; + /// + /// Event to handle when the cell should cancel editing. + /// + public event EventHandler CancelEdit; + /// + /// Event to handle when the cell should commit editing. + /// + public event EventHandler CommitEdit; + + /// + /// Triggers the event. + /// + /// Cell event arguments + protected virtual void OnBeginEdit(CellEventArgs e) => BeginEdit?.Invoke(this, e); + + /// + /// Triggers the event. + /// + /// Cell event arguments + protected virtual void OnCancelEdit(CellEventArgs e) => CancelEdit?.Invoke(this, e); + + /// + /// Triggers the event. + /// + /// Cell event arguments + protected virtual void OnCommitEdit(CellEventArgs e) => CommitEdit?.Invoke(this, e); + static readonly object PaintEvent = new object(); /// @@ -415,6 +491,21 @@ protected virtual void OnPaint(CellPaintEventArgs args) /// Raises the paint event. /// void OnPaint(CustomCell widget, CellPaintEventArgs args); + + /// + /// Raises the BeginEdit event. + /// + void OnBeginEdit(CustomCell widget, CellEventArgs args); + + /// + /// Raises the CancelEdit event. + /// + void OnCancelEdit(CustomCell widget, CellEventArgs args); + + /// + /// Raises the CommitEdit event. + /// + void OnCommitEdit(CustomCell widget, CellEventArgs args); } /// @@ -466,6 +557,33 @@ public void OnPaint(CustomCell widget, CellPaintEventArgs args) using (widget.Platform.Context) widget.OnPaint(args); } + + /// + /// Raises the BeginEdit event. + /// + public void OnBeginEdit(CustomCell widget, CellEventArgs args) + { + using (widget.Platform.Context) + widget.OnBeginEdit(args); + } + + /// + /// Raises the CancelEdit event. + /// + public void OnCancelEdit(CustomCell widget, CellEventArgs args) + { + using (widget.Platform.Context) + widget.OnCancelEdit(args); + } + + /// + /// Raises the CommitEdit event. + /// + public void OnCommitEdit(CustomCell widget, CellEventArgs args) + { + using (widget.Platform.Context) + widget.OnCommitEdit(args); + } } static readonly object callback = new Callback(); @@ -474,10 +592,7 @@ public void OnPaint(CustomCell widget, CellPaintEventArgs args) /// Gets an instance of an object used to perform callbacks to the widget from handler implementations /// /// The callback. - protected override object GetCallback() - { - return callback; - } + protected override object GetCallback() => callback; } } diff --git a/src/Shared/MutableCellEventArgs.cs b/src/Shared/MutableCellEventArgs.cs index 838a234e5e..67d30cbd6a 100644 --- a/src/Shared/MutableCellEventArgs.cs +++ b/src/Shared/MutableCellEventArgs.cs @@ -6,8 +6,8 @@ namespace Eto.Shared { public class MutableCellEventArgs : CellEventArgs { - public MutableCellEventArgs(Grid grid, Cell cell, int row, object item, CellStates cellState) - : base(grid, cell, row, item, cellState) + public MutableCellEventArgs(Grid grid, Cell cell, int row, int column, object item, CellStates cellState, Control control) + : base(grid, cell, row, column, item, cellState, control) { } @@ -30,6 +30,11 @@ public void SetItem(object item) { Item = item; } + + public void SetControl(Control control) + { + Control = control; + } } } diff --git a/test/Eto.Test/Sections/Controls/GridViewSection.cs b/test/Eto.Test/Sections/Controls/GridViewSection.cs index 7ea43964ea..c79b238399 100644 --- a/test/Eto.Test/Sections/Controls/GridViewSection.cs +++ b/test/Eto.Test/Sections/Controls/GridViewSection.cs @@ -455,12 +455,12 @@ ContextMenu CreateContextMenu(GridView grid) protected virtual void LogEvents(GridView control) { - control.CellEditing += (sender, e) => Log.Write(control, $"BeginCellEdit, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); - control.CellEdited += (sender, e) => Log.Write(control, $"EndCellEdit, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); - control.SelectionChanged += (sender, e) => Log.Write(control, $"Selection Changed, Rows: {SelectedRowsString(control)}"); - control.ColumnHeaderClick += (sender, e) => Log.Write(control, $"Column Header Clicked: {e.Column.HeaderText}"); - control.CellClick += (sender, e) => Log.Write(control, $"Cell Clicked, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); - control.CellDoubleClick += (sender, e) => Log.Write(control, $"Cell Double Clicked, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); + control.CellEditing += (sender, e) => Log.Write(control, $"CellEditing, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); + control.CellEdited += (sender, e) => Log.Write(control, $"CellEdited, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); + control.SelectionChanged += (sender, e) => Log.Write(control, $"SelectionChanged, Rows: {SelectedRowsString(control)}"); + control.ColumnHeaderClick += (sender, e) => Log.Write(control, $"ColumnHeaderClick: {e.Column.HeaderText}"); + control.CellClick += (sender, e) => Log.Write(control, $"CellClick, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); + control.CellDoubleClick += (sender, e) => Log.Write(control, $"CellDoubleClick, Row: {e.Row}, Column: {e.Column}, Item: {e.Item}, GridColumn: {e.GridColumn}, IsEditing: {control.IsEditing}"); control.MouseDown += (sender, e) => Log.Write(control, $"MouseDown, Buttons: {e.Buttons}, Location: {e.Location}"); control.MouseDoubleClick += (sender, e) => Log.Write(control, $"MouseDoubleClick, Buttons: {e.Buttons}, Location: {e.Location}"); diff --git a/test/Eto.Test/UnitTests/Forms/Controls/GridViewTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/GridViewTests.cs old mode 100644 new mode 100755 index afd2619d67..2e77459872 --- a/test/Eto.Test/UnitTests/Forms/Controls/GridViewTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/GridViewTests.cs @@ -12,9 +12,137 @@ namespace Eto.Test.UnitTests.Forms.Controls { + public abstract class GridTests : TestBase + where T: Grid, new() + { + class GridTestItem : TreeGridItem + { + public string Text { get; set; } + } + + [Test, ManualTest] + public void BeginEditShoudWorkOnCustomCells() + { + ManualForm("The custom cell should go in edit mode when clicking the BeginEdit button", form => + { + var grid = new T(); + grid.ShowHeader = true; + grid.AllowMultipleSelection = true; + + string CellInfo(GridViewCellEventArgs e) => $"Row: {e.Row}, Column: {e.Column}"; + string CellEditInfo(CellEventArgs e) => $"Row: {e.Row}"; + void AddLogging(CustomCell cell) + { + cell.BeginEdit += (sender, e) => Log.Write(sender, $"BeginEdit {CellEditInfo(e)}, Grid.IsEditing: {grid.IsEditing}"); + cell.CommitEdit += (sender, e) => Log.Write(sender, $"CommitEdit {CellEditInfo(e)}, Grid.IsEditing: {grid.IsEditing}"); + cell.CancelEdit += (sender, e) => Log.Write(sender, $"CancelEdit {CellEditInfo(e)}, Grid.IsEditing: {grid.IsEditing}"); + + if (!CustomCell.SupportsControlView) + { + cell.GetPreferredWidth = args => 100; + cell.Paint += (sender, e) => { + e.Graphics.DrawText(SystemFonts.Default(), Brushes.Black, e.ClipRectangle, "Cell", alignment: FormattedTextAlignment.Center); + }; + } + + } + + grid.CellEditing += (sender, e) => Log.Write(sender, $"CellEditing {CellInfo(e)}, Grid.IsEditing: {grid.IsEditing}"); + grid.CellEdited += (sender, e) => Log.Write(sender, $"CellEdited {CellInfo(e)}, Grid.IsEditing: {grid.IsEditing}"); + var customCell = new CustomCell(); + customCell.CreateCell = args => + { + var textBox = new TextBox { ShowBorder = false, BackgroundColor = Colors.Transparent }; + + if (!Platform.Instance.IsMac) + { + textBox.GotFocus += (sender, e) => textBox.BackgroundColor = SystemColors.ControlBackground; + textBox.LostFocus += (sender, e) => textBox.BackgroundColor = Colors.Transparent; + + // ugly, there should be a better way to do this.. + var colorBinding = textBox.Bind(c => c.TextColor, args, Binding.Property((CellEventArgs a) => a.CellTextColor).Convert(c => args.IsEditing ? SystemColors.ControlText : c)); + args.PropertyChanged += (sender, e) => { + if (e.PropertyName == nameof(CellEventArgs.IsEditing)) + colorBinding.Update(); + }; + } + else + { + // macOS handles colors more automaticcally for a TextBox + } + + textBox.TextBinding.BindDataContext((GridTestItem i) => i.Text); + + return textBox; + }; + AddLogging(customCell); + grid.Columns.Add(new GridColumn { DataCell = customCell, Editable = true, HeaderText = "CustomTextBox" }); + + var customCell2 = new CustomCell(); + customCell2.CreateCell = args => + { + var dropDown = new DropDown { Items = { "Item 1", "Item 2", "Item 3" }}; + + return dropDown; + }; + AddLogging(customCell2); + grid.Columns.Add(new GridColumn { DataCell = customCell2, Editable = true, HeaderText = "CustomDropDown" }); + + var customCell3 = new CustomCell(); + customCell3.CreateCell = args => + { + var checkBox = new CheckBox(); + + return checkBox; + }; + AddLogging(customCell3); + grid.Columns.Add(new GridColumn { DataCell = customCell3, Editable = true, HeaderText = "CustomCheckBox" }); + + grid.Columns.Add(new GridColumn { DataCell = new TextBoxCell(nameof(GridTestItem.Text)), HeaderText = "TextBoxCell", Editable = true }); + + var list = new List(); + list.Add(new GridTestItem { Text = "Item 1" }); + list.Add(new GridTestItem { Text = "Item 2" }); + list.Add(new GridTestItem { Text = "Item 3" }); + SetDataStore(grid, list); + + // using MouseDown so the buttons don't get focus + var beginEditButton = new Button { Text = "BeginEdit" }; + beginEditButton.MouseDown += (sender, e) => { + grid.BeginEdit(1, 0); + e.Handled = true; + }; + + var commitEditButton = new Button { Text = "CommitEdit" }; + commitEditButton.MouseDown += (sender, e) => { + grid.CommitEdit(); + e.Handled = true; + }; + + var cancelEditButton = new Button { Text = "CancelEdit" }; + cancelEditButton.MouseDown += (sender, e) => { + grid.CancelEdit(); + e.Handled = true; + }; + + return new TableLayout( + TableLayout.Horizontal(4, beginEditButton, commitEditButton, cancelEditButton, null), + grid + ); + }); + } + + protected abstract void SetDataStore(T grid, IEnumerable dataStore); + } + [TestFixture] - public class GridViewTests : TestBase + public class GridViewTests : GridTests { + protected override void SetDataStore(GridView grid, IEnumerable dataStore) + { + grid.DataStore = dataStore; + } + [Test, ManualTest] public void CellClickShouldHaveMouseInformation() {