From 5c17e7739f1e6537662523f6e3611f944ef2ed16 Mon Sep 17 00:00:00 2001 From: Fabian Stettler Date: Tue, 5 Aug 2025 19:40:46 +0200 Subject: [PATCH 1/2] Improve the cell edit to allow user to cancel edit with ESC in supported CellEditors for Text/Numbers. - Enter => Accept/Save edit - ESC => Discard edit This is more like standard behavior in text edic cells. Fixes #199 --- src/Columns/TableViewNumberColumn.cs | 9 +- src/Columns/TableViewTextColumn.cs | 9 +- src/Helpers/EditingHelper.cs | 260 +++++++++++++++++++++++++++ 3 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 src/Helpers/EditingHelper.cs diff --git a/src/Columns/TableViewNumberColumn.cs b/src/Columns/TableViewNumberColumn.cs index e46c7c9..4d670e5 100644 --- a/src/Columns/TableViewNumberColumn.cs +++ b/src/Columns/TableViewNumberColumn.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using WinUI.TableView.Helpers; namespace WinUI.TableView; @@ -29,7 +30,7 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat } /// - /// Generates a NumberBox element for editing the cell. + /// Generates a NumberBox element for editing the cell with ESC/Enter key support. /// /// The cell for which the editing element is generated. /// The data item associated with the cell. @@ -37,10 +38,14 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) { var numberBox = new NumberBox(); - numberBox.SetBinding(NumberBox.ValueProperty, Binding); + + // Add ESC/Enter key handling + EditingHelper.AddKeyHandling(numberBox, cell, dataItem, Binding.Path?.Path); + #if !WINDOWS numberBox.DataContext = dataItem; #endif + return numberBox; } } diff --git a/src/Columns/TableViewTextColumn.cs b/src/Columns/TableViewTextColumn.cs index 5f02e16..5ff2432 100644 --- a/src/Columns/TableViewTextColumn.cs +++ b/src/Columns/TableViewTextColumn.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using WinUI.TableView.Helpers; namespace WinUI.TableView; @@ -27,7 +28,7 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat } /// - /// Generates a TextBox element for editing the cell. + /// Generates a TextBox element for editing the cell with ESC/Enter key support. /// /// The cell for which the editing element is generated. /// The data item associated with the cell. @@ -35,10 +36,14 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) { var textBox = new TextBox(); - textBox.SetBinding(TextBox.TextProperty, Binding); + + // Add ESC/Enter key handling + EditingHelper.AddKeyHandling(textBox, cell, dataItem, Binding.Path?.Path); + #if !WINDOWS textBox.DataContext = dataItem; #endif + return textBox; } } diff --git a/src/Helpers/EditingHelper.cs b/src/Helpers/EditingHelper.cs new file mode 100644 index 0000000..4f6d6d7 --- /dev/null +++ b/src/Helpers/EditingHelper.cs @@ -0,0 +1,260 @@ +using Microsoft.UI.Xaml.Controls; +using System; +using Windows.System; + +namespace WinUI.TableView.Helpers; + +/// +/// Helper class for handling ESC/Enter key functionality in editable columns +/// +internal static class EditingHelper +{ + /// + /// Adds ESC/Enter key handling to a TextBox editing element + /// + /// The TextBox editing element + /// The TableView cell + /// The data item + /// The property path for binding + public static void AddKeyHandling(TextBox textBox, TableViewCell cell, object? dataItem, string? propertyPath) + { + // Store original value for ESC cancellation + var originalValue = GetPropertyValue(dataItem, propertyPath); + textBox.Tag = originalValue; + + // Set initial text value + textBox.Text = originalValue?.ToString() ?? string.Empty; + + // Handle ESC and Enter key events + textBox.KeyDown += (sender, args) => + { + if (args.Key == VirtualKey.Escape) + { + // Cancel editing: restore original value + textBox.Text = textBox.Tag?.ToString() ?? string.Empty; + args.Handled = true; + EndEditing(cell); + } + else if (args.Key == VirtualKey.Enter) + { + // Commit changes: update data source manually + CommitTextValue(textBox, dataItem, propertyPath); + args.Handled = true; + EndEditing(cell); + } + }; + + // Commit changes when focus is lost + textBox.LostFocus += (sender, args) => + { + if (cell.TableView?.IsEditing == true) + { + CommitTextValue(textBox, dataItem, propertyPath); + } + }; + } + + /// + /// Adds ESC/Enter key handling to a NumberBox editing element + /// + /// The NumberBox editing element + /// The TableView cell + /// The data item + /// The property path for binding + public static void AddKeyHandling(NumberBox numberBox, TableViewCell cell, object? dataItem, string? propertyPath) + { + // Store original value for ESC cancellation + var originalValue = GetPropertyValue(dataItem, propertyPath); + numberBox.Tag = originalValue; + + // Set initial value + if (originalValue != null) + { + try + { + numberBox.Value = Convert.ToDouble(originalValue); + } + catch + { + numberBox.Value = 0; + } + } + + // Handle ESC and Enter key events + numberBox.KeyDown += (sender, args) => + { + if (args.Key == VirtualKey.Escape) + { + // Cancel editing: restore original value + if (numberBox.Tag != null) + { + try + { + numberBox.Value = Convert.ToDouble(numberBox.Tag); + } + catch + { + numberBox.Value = 0; + } + } + args.Handled = true; + EndEditing(cell); + } + else if (args.Key == VirtualKey.Enter) + { + // Commit changes: update data source manually + CommitNumberValue(numberBox, dataItem, propertyPath); + args.Handled = true; + EndEditing(cell); + } + }; + + // Commit changes when focus is lost + numberBox.LostFocus += (sender, args) => + { + if (cell.TableView?.IsEditing == true) + { + CommitNumberValue(numberBox, dataItem, propertyPath); + } + }; + } + + /// + /// Ends editing mode and refreshes the cell + /// + private static void EndEditing(TableViewCell cell) + { + cell.TableView?.SetIsEditing(false); + cell.SetElement(); + } + + /// + /// Commits TextBox value to the data source + /// + private static void CommitTextValue(TextBox textBox, object? dataItem, string? propertyPath) + { + if (dataItem == null || string.IsNullOrEmpty(propertyPath)) + return; + + try + { + var property = dataItem.GetType().GetProperty(propertyPath); + if (property != null && property.CanWrite) + { + var convertedValue = ConvertValue(textBox.Text, property.PropertyType); + property.SetValue(dataItem, convertedValue); + } + } + catch + { + // Ignore conversion errors + } + } + + /// + /// Commits NumberBox value to the data source + /// + private static void CommitNumberValue(NumberBox numberBox, object? dataItem, string? propertyPath) + { + if (dataItem == null || string.IsNullOrEmpty(propertyPath)) + return; + + try + { + var property = dataItem.GetType().GetProperty(propertyPath); + if (property != null && property.CanWrite) + { + var convertedValue = ConvertToPropertyType(numberBox.Value, property.PropertyType); + property.SetValue(dataItem, convertedValue); + } + } + catch + { + // Ignore conversion errors + } + } + + /// + /// Converts string value to target property type + /// + private static object? ConvertValue(string textValue, Type targetType) + { + if (string.IsNullOrEmpty(textValue)) + { + return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + } + + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (underlyingType == typeof(string)) + return textValue; + + try + { + return Convert.ChangeType(textValue, underlyingType); + } + catch + { + return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + } + } + + /// + /// Converts double value to target numeric property type + /// + private static object? ConvertToPropertyType(double value, Type targetType) + { + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + try + { + if (underlyingType == typeof(int)) + return (int)value; + else if (underlyingType == typeof(long)) + return (long)value; + else if (underlyingType == typeof(float)) + return (float)value; + else if (underlyingType == typeof(double)) + return value; + else if (underlyingType == typeof(decimal)) + return (decimal)value; + else if (underlyingType == typeof(short)) + return (short)value; + else if (underlyingType == typeof(byte)) + return (byte)value; + else if (underlyingType == typeof(uint)) + return (uint)value; + else if (underlyingType == typeof(ulong)) + return (ulong)value; + else if (underlyingType == typeof(ushort)) + return (ushort)value; + else if (underlyingType == typeof(sbyte)) + return (sbyte)value; + else + return Convert.ChangeType(value, underlyingType); + } + catch + { + return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + } + } + + /// + /// Helper method to get property value from data item + /// + private static object? GetPropertyValue(object? dataItem, string? propertyPath) + { + if (dataItem == null || string.IsNullOrEmpty(propertyPath)) + return null; + + try + { + var property = dataItem.GetType().GetProperty(propertyPath); + return property?.GetValue(dataItem); + } + catch + { + return null; + } + } +} \ No newline at end of file From 671fa633a395661d1bb740484425a2d7d7fe52a0 Mon Sep 17 00:00:00 2001 From: Fabian Stettler Date: Tue, 5 Aug 2025 21:35:53 +0200 Subject: [PATCH 2/2] Improved version. Only TextBox editing elements are supported. NumberBox has internal logic, and should support esc, but does not work in TableView somehow. --- src/Columns/TableViewNumberColumn.cs | 11 +-- src/Helpers/EditingHelper.cs | 135 +-------------------------- 2 files changed, 7 insertions(+), 139 deletions(-) diff --git a/src/Columns/TableViewNumberColumn.cs b/src/Columns/TableViewNumberColumn.cs index 4d670e5..e1ffee4 100644 --- a/src/Columns/TableViewNumberColumn.cs +++ b/src/Columns/TableViewNumberColumn.cs @@ -1,6 +1,5 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using WinUI.TableView.Helpers; namespace WinUI.TableView; @@ -30,7 +29,7 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat } /// - /// Generates a NumberBox element for editing the cell with ESC/Enter key support. + /// Generates a NumberBox element for editing the cell. /// /// The cell for which the editing element is generated. /// The data item associated with the cell. @@ -38,14 +37,10 @@ public override FrameworkElement GenerateElement(TableViewCell cell, object? dat public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) { var numberBox = new NumberBox(); - - // Add ESC/Enter key handling - EditingHelper.AddKeyHandling(numberBox, cell, dataItem, Binding.Path?.Path); - + numberBox.SetBinding(NumberBox.ValueProperty, Binding); #if !WINDOWS numberBox.DataContext = dataItem; #endif - return numberBox; } -} +} \ No newline at end of file diff --git a/src/Helpers/EditingHelper.cs b/src/Helpers/EditingHelper.cs index 4f6d6d7..93f9f8c 100644 --- a/src/Helpers/EditingHelper.cs +++ b/src/Helpers/EditingHelper.cs @@ -6,6 +6,7 @@ namespace WinUI.TableView.Helpers; /// /// Helper class for handling ESC/Enter key functionality in editable columns +/// Only TextBox editing elements are supported /// internal static class EditingHelper { @@ -54,74 +55,9 @@ public static void AddKeyHandling(TextBox textBox, TableViewCell cell, object? d }; } - /// - /// Adds ESC/Enter key handling to a NumberBox editing element - /// - /// The NumberBox editing element - /// The TableView cell - /// The data item - /// The property path for binding - public static void AddKeyHandling(NumberBox numberBox, TableViewCell cell, object? dataItem, string? propertyPath) - { - // Store original value for ESC cancellation - var originalValue = GetPropertyValue(dataItem, propertyPath); - numberBox.Tag = originalValue; - - // Set initial value - if (originalValue != null) - { - try - { - numberBox.Value = Convert.ToDouble(originalValue); - } - catch - { - numberBox.Value = 0; - } - } - - // Handle ESC and Enter key events - numberBox.KeyDown += (sender, args) => - { - if (args.Key == VirtualKey.Escape) - { - // Cancel editing: restore original value - if (numberBox.Tag != null) - { - try - { - numberBox.Value = Convert.ToDouble(numberBox.Tag); - } - catch - { - numberBox.Value = 0; - } - } - args.Handled = true; - EndEditing(cell); - } - else if (args.Key == VirtualKey.Enter) - { - // Commit changes: update data source manually - CommitNumberValue(numberBox, dataItem, propertyPath); - args.Handled = true; - EndEditing(cell); - } - }; - - // Commit changes when focus is lost - numberBox.LostFocus += (sender, args) => - { - if (cell.TableView?.IsEditing == true) - { - CommitNumberValue(numberBox, dataItem, propertyPath); - } - }; - } - /// /// Ends editing mode and refreshes the cell - /// + /// /// private static void EndEditing(TableViewCell cell) { cell.TableView?.SetIsEditing(false); @@ -149,30 +85,7 @@ private static void CommitTextValue(TextBox textBox, object? dataItem, string? p { // Ignore conversion errors } - } - - /// - /// Commits NumberBox value to the data source - /// - private static void CommitNumberValue(NumberBox numberBox, object? dataItem, string? propertyPath) - { - if (dataItem == null || string.IsNullOrEmpty(propertyPath)) - return; - - try - { - var property = dataItem.GetType().GetProperty(propertyPath); - if (property != null && property.CanWrite) - { - var convertedValue = ConvertToPropertyType(numberBox.Value, property.PropertyType); - property.SetValue(dataItem, convertedValue); - } - } - catch - { - // Ignore conversion errors - } - } + } /// /// Converts string value to target property type @@ -197,47 +110,7 @@ private static void CommitNumberValue(NumberBox numberBox, object? dataItem, str { return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; } - } - - /// - /// Converts double value to target numeric property type - /// - private static object? ConvertToPropertyType(double value, Type targetType) - { - var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; - - try - { - if (underlyingType == typeof(int)) - return (int)value; - else if (underlyingType == typeof(long)) - return (long)value; - else if (underlyingType == typeof(float)) - return (float)value; - else if (underlyingType == typeof(double)) - return value; - else if (underlyingType == typeof(decimal)) - return (decimal)value; - else if (underlyingType == typeof(short)) - return (short)value; - else if (underlyingType == typeof(byte)) - return (byte)value; - else if (underlyingType == typeof(uint)) - return (uint)value; - else if (underlyingType == typeof(ulong)) - return (ulong)value; - else if (underlyingType == typeof(ushort)) - return (ushort)value; - else if (underlyingType == typeof(sbyte)) - return (sbyte)value; - else - return Convert.ChangeType(value, underlyingType); - } - catch - { - return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; - } - } + } /// /// Helper method to get property value from data item