1

I have created a custom CollectionEditor but I want to validate my collection when the user clicks the 'OK' button. I've tried the following:

protected override CollectionForm CreateCollectionForm()
{
    _form = base.CreateCollectionForm();                        
    _form.FormClosing += _form_FormClosing;

    return _form;
}

So that when the user clicks OK it fires the _form_Closing event. This works however when I do this:

private void _form_FormClosing(object sender, FormClosingEventArgs e)
{
     e.Cancel = !Validate();                    
}

And Validate returns false (telling the form not to close) all of the existing members of the collection are removed from the UI. Surely the items of the collection shouldn't disappear from the UI?

Is there something else I need to call?

Brian
  • 5,069
  • 7
  • 37
  • 47
mxcolin
  • 11
  • 1
  • Unfortunately, the CollectionForm is not designed for this kind of trickery. In fact, it empties the right-side listbox when you press ok, no matter how (and also setup your object with what was in that listbox). You could try to enable/disable the ok button (which is _form.AcceptButton) depending on your context, but this requires you to react to change, not wait for the user to press the ok button. – Simon Mourier Feb 17 '16 at 13:38
  • Ouch. That's messy. It would require default values for items in the collection that are valid and in this case they can never be valid. Oh well, this gives me something to go on. – mxcolin Feb 17 '16 at 17:19
  • I don't suppose there is a way to capture the OK button being clicked. – mxcolin Feb 17 '16 at 17:21
  • There is but there doesn't appear to be anything you can do at that point. – mxcolin Feb 17 '16 at 17:30

2 Answers2

0

OK so it's not elegant but it does work.

Get the ListBox like so

_listBox = _form.Controls[0].Controls[4] as ListBox;

Store it as a member variable and then handle the MouseDown event on the OK button like so

Button btnOK = _form.AcceptButton as Button;            
btnOK.MouseDown += btnOK_MouseDown;

Then create a list or array of objects in the class and copy them into the array on MouseDown (you can't do MouseClick as by then they are gone).

void btnOK_MouseDown(object sender, MouseEventArgs e)
{
    _objects = new List<object>();

    foreach (object listItem in _listBox.Items)
    {
        _objects.Add(listItem);
    }           
}       

Then on Form_Closing if the collection doesn't pass validation then add them back in.

if(!CheckValidEntities(_value as IEnumerable<Entity>))
{
    e.Cancel = true;

    foreach (object listItem in _objects)
    {
        _listBox.Items.Add(listItem);
    }                       
}

I don't love it and it is a little hacky but it seems to work.

mxcolin
  • 11
  • 1
0

Still a hack but this preserves the state of the dialog. This works no matter if the button was clicked with the mouse or the keyboard.

        protected override CollectionForm CreateCollectionForm()
        {
            this.Form = base.CreateCollectionForm();
            var okButton = this.Form.AcceptButton as Button;

            // replace the OK button current eventHandler.
            var okClickDelegate = this.RemoveClickEvent(okButton);
            okButton.Click += (sender, e) =>
            {
                // Validate your items
                var isValid = this.ValidateItems();
                if (isValid)
                {
                    okClickDelegate.DynamicInvoke(sender, e);
                }

                this.preventClose = !isValid;
            };
        }

        private void Form_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e)
        {
            e.Cancel = this.preventClose;

            this.preventClose = false;
        }

        /// <summary>
        /// Removes the click event handler of a button.
        /// </summary>
        /// <param name="b">The button to remove the click event.</param>
        /// <returns>A Delegate representing the remove event.</returns>
        private Delegate RemoveClickEvent(Button b)
        {
            FieldInfo f1 = typeof(Control).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
            object obj = f1.GetValue(b);
            PropertyInfo pi = b.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
            EventHandlerList list = (EventHandlerList)pi.GetValue(b, null);

            var handler = list[obj];

            list.RemoveHandler(obj, handler);

            return handler;
        }

The function to remove the events was taken from: https://stackoverflow.com/a/91853/1698342

uron83
  • 1
  • 1