1

I have a DevExpress GridView control bound to a binding list. I'm trying to emulate SQL Server Management Studio, where if you delete a row and it fails due to a constraint, the row remains in the grid. I handle this by having a subclass of BindingList which overrides RemoveAt and tells the object to delete itself from the DB before base.RemoveAt is called. However, this seems to cause an exception which crashes the application. I also haven't found any events from the grid to handle the error to prevent this. Here's the code:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Windows.Forms;

namespace DXGridRemoveItem {
    static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    public sealed class CustomBindingList : BindingList<BusinessObject> {
        public CustomBindingList() {
            AllowEdit = true;
            AllowRemove = true;
            AllowNew = true;
        }

        protected override void RemoveItem(int index) {
            var item = this[index];
            item.Save();
            base.RemoveItem(index);
        }
    }

    [Serializable]
    public sealed class ObjectInUseException : Exception {
        public ObjectInUseException() { }

        public ObjectInUseException(string message) : base(message) { }

        public ObjectInUseException(string message, Exception innerException) : base(message, innerException) { }

        private ObjectInUseException(SerializationInfo info, StreamingContext context) : base(info, context) { }
    }

    public sealed class BusinessObject : IEditableObject, INotifyPropertyChanged {
        private static int nextId;

        private DataStorage currentData;
        private DataStorage savedData;
        private bool editing;

        public BusinessObject() {
            currentData = new DataStorage(nextId++);
        }

        public int Id => currentData.Id;

        public string Name {
            get => currentData.Name;
            set {
                if (currentData.Name != value) {
                    currentData.Name = value;
                    OnPropertyChanged(nameof(Name));
                }
            }
        }

        public void Save() {
            if (editing) { throw new InvalidOperationException("Cannot save object while it is being edited."); }

            // This happens because we tried to remove a row and a SQL constraint is preventing it
            throw new ObjectInUseException();
        }

        public void BeginEdit() {
            if (!editing) {
                editing = true;
                savedData = currentData;
            }
        }

        public void EndEdit() {
            if (editing) {
                savedData = null;
                editing = false;
            }
        }

        public void CancelEdit() {
            if (editing) {
                currentData = savedData;
                editing = false;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private class DataStorage {
            public DataStorage(int id) {
                Id = id;
            }

            public int Id { get; set; }

            public string Name { get; set; }
        }
    }
}

And the code for Form1; a ToolStrip with a Delete button, a DX GridControl are added, and a BindingSource have been added. The BindingSource's DataSource is the CustomBindingList, and the grid's DataSource is the BindingSource.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DXGridRemoveItem {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        protected override void OnShown(EventArgs e) {
            var list = new CustomBindingList {
                new BusinessObject {Name = "Name 1"},
                new BusinessObject {Name = "Name 2"},
                new BusinessObject {Name = "Name 3"},
                new BusinessObject {Name = "Name 4"}
            };

            bindingSource1.DataSource = list;

            base.OnShown(e);
        }

        private void toolStripButton1_Click(object sender, EventArgs e) {
            gridView1.DeleteSelectedRows();
        }
    }
}
Andy
  • 8,432
  • 6
  • 38
  • 76
  • `... an exception which crashes the application ...` Could you elaborate? Please, post the exception's message, type and stack trace. – default locale Nov 15 '17 at 03:45
  • 1
    @defaultlocale Its the `ObjectInUseException` that is thrown in the `RemoveItem`. I know why the exception is occurring, I'm asking how I can handle it without crashing the app or ending up in the `UnhandledException` handler (which my real application uses, but I'm trying to handle things gracefully on the form). – Andy Nov 15 '17 at 22:24
  • Oh, I didn't understand the question, thanks! Anyway, it looks like `XtraGrid` doesn't handle data binding exceptions gracefully. You can save items in [`RowDeleting`](https://documentation.devexpress.com/WindowsForms/DevExpress.XtraGrid.Views.Base.ColumnView.RowDeleting.event) event, where you can also [cancel](https://documentation.devexpress.com/CoreLibraries/DevExpress.Data.RowDeletingEventArgs.Cancel.property) the item removal if exception occurs. Is it acceptable for you to move the logic into UI? – default locale Nov 16 '17 at 05:09

0 Answers0