Click here to Skip to main content
15,892,059 members
Articles / Desktop Programming / Windows Forms

Exposing Dynamic Events in the WinForms Designer

Rate me:
Please Sign up or sign in to vote.
4.69/5 (17 votes)
20 Jan 2011CPOL9 min read 35.6K   927   29  
A solution to declaring dynamic events on control arrays at design time
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Forms;
using System.Windows.Forms.Design;

// RightToCopy & PublishAndPerish: OrlandoCurioso 2011

namespace DynamicEvents.Design
{
    /// <summary>
    /// Designer for <see cref="ButtonContainer"/> controls.
    /// Supports seperate 'ButtonClick' events for individual items.
    /// </summary>
    internal class ButtonContainerDesigner : ControlDesigner, IServiceProvider
    {
        #region Fields

        private static readonly Type Event_HandlerType = Constants.Event_HandlerType;
        private const string Event_DummyDesign = Constants.Event_DummyDesign;
        private const string formatDescription = Constants.DESCR_DesignEvent;

        private CustomEventDescriptor interceptedEventDescriptor;
        private bool reEntrantCodeCancel;
        private bool reEntrantCodeSet;

        #endregion

        #region Initialize & Dispose

        public override void Initialize(IComponent component)
        {
            base.Initialize(component);
            attachEventhandler();
        }

        protected override void Dispose(bool disposing)
        {
            detachEventhandler();
            base.Dispose(disposing);
        }

        private void attachEventhandler()
        {
            IComponentChangeService ccs = (IComponentChangeService)GetService(typeof(IComponentChangeService));
            if (ccs != null)
            {
                ccs.ComponentChanging += new ComponentChangingEventHandler(ccs_ComponentChanging);
            }

            Debug.Assert(ccs != null);
        }

        private void detachEventhandler()
        {
            IComponentChangeService ccs = (IComponentChangeService)GetService(typeof(IComponentChangeService));
            if (ccs != null)
            {
                ccs.ComponentChanging -= new ComponentChangingEventHandler(ccs_ComponentChanging);
            }
        }

        #endregion

        #region Generated events

#pragma warning disable 67
        /// <summary>
        /// Dummy event at design time.
        /// Name == <see cref="Event_DummyDesign"/>; Type == <see cref="Event_HandlerType"/>
        /// </summary>
        public event EventHandler ButtonClick_;
#pragma warning restore 67

        private IEnumerable<EnumMemberAttribute> createAttributes()
        {
            ButtonContainer control = (ButtonContainer)Control;
            List<EnumMemberAttribute> attributes = new List<EnumMemberAttribute>();

            foreach (string name in Enum.GetNames(control.EnumType))
            {
                EnumMemberAttribute attr = new EnumMemberAttribute(control.EnumType, Enum.Parse(control.EnumType, name), name);
                attributes.Add(attr);
            }

            return attributes;
        }

        protected override void PreFilterEvents(IDictionary events)
        {
            //base.PreFilterEvents(events);

            Type componentType = GetType();
            List<CustomEventDescriptor> generatedEventDescriptors = new List<CustomEventDescriptor>();

            foreach (EnumMemberAttribute enumMemberAttribute in createAttributes())
            {
                string enumName = enumMemberAttribute.EnumName;

                EventDescriptor ed = TypeDescriptor.CreateEvent(componentType, Event_DummyDesign, Event_HandlerType,
                    new DesignOnlyAttribute(true),
                    new DisplayNameAttribute(Event_DummyDesign + enumName),
                    new DescriptionAttribute(string.Format(CultureInfo.CurrentCulture, formatDescription, enumName)),
                    new MergablePropertyAttribute(false),
                    enumMemberAttribute);

                // must use proxy EventDescriptor. Using TypeAlias with TypeDescriptor.CreateEvent() does not seem to work !?!
                CustomEventDescriptor ced = new CustomEventDescriptor(ed, enumMemberAttribute, null);

                generatedEventDescriptors.Add(ced);

                events.Add(Event_DummyDesign + enumName, ced);
            }

            EventPropertyTypeConverter.ExchangeTypeConverter(this, generatedEventDescriptors);
        }

        void ccs_ComponentChanging(object sender, ComponentChangingEventArgs e)
        {
            if (e.Component != Component || e.Member == null)
            {
                return;
            }

            if (reEntrantCodeSet)
            {
                // e.Member is either interceptedEventDescriptor or it's associated EventPropertyDescriptor
                Debug.Assert(e.Member.Attributes[typeof(EnumMemberAttribute)] == interceptedEventDescriptor.Attributes[typeof(EnumMemberAttribute)]);
                return;
            }

            if (reEntrantCodeCancel)
            {
                // e.Member is never interceptedEventDescriptor or it's associated EventPropertyDescriptor
                Debug.Assert(e.Member.Attributes[typeof(EnumMemberAttribute)] != interceptedEventDescriptor.Attributes[typeof(EnumMemberAttribute)]);

                // reEntrantCodeCancel: {e.Member.GetType().FullName}
                // Cancel triggered Undo operation for a previous intercepted EventPropertyDescriptor or other descriptor
                throw CheckoutException.Canceled;
            }

            CustomEventDescriptor ced = e.Member as CustomEventDescriptor;
            if (ced == null)
            {
                return;
            }

            //-- EventPropertyDescriptor is setting value

            Debug.Assert(interceptedEventDescriptor == null);
            interceptedEventDescriptor = ced;

            // -- process EventDescriptor, after EventPropertyDescriptor silently cancelled DesignerTransaction, causing new ComponentChanging event
            reEntrantCodeCancel = true;
            Application.Idle += new EventHandler(Application_Idle);
            throw CheckoutException.Canceled;
        }

        void Application_Idle(object sender, EventArgs e)
        {
            Application.Idle -= Application_Idle;

            reEntrantCodeCancel = false;
            reEntrantCodeSet = true;

            Debug.Assert(interceptedEventDescriptor != null);
            Debug.Assert(((IDesignerHost)GetService(typeof(IDesignerHost))).InTransaction == false);

            try
            {
                IEventBindingService svc = (IEventBindingService)GetService(typeof(IEventBindingService));
                PropertyDescriptor eventProperty = svc.GetEventProperty(interceptedEventDescriptor);
                EnumMemberAttribute enumMemberAttribute = (EnumMemberAttribute)interceptedEventDescriptor.Attributes[typeof(EnumMemberAttribute)];

                object curValue = eventProperty.GetValue(Component);
                if (curValue == null)
                {
                    // -- create new listener or attach existing having identical name

                    string methodName = svc.CreateUniqueMethodName(Component, interceptedEventDescriptor);
                    methodName += enumMemberAttribute.EnumName;

                    // As the listener name is always created here, despite any value specified by user, only our listener name is ever used -> no compatible methods

                    // name conflicts are unlikely
                    // CanDo: validate methodName via CodeTypeDeclaration (obtainable as service)

                    eventProperty.SetValue(Component, methodName);

                    // IEventBindingService creates correct listener method and incorrect CodeAttachEventStatement for the dummy event.
                    // DesignEventSerializer will exchange the CodeAttachEventStatement for 'component.AddEventHandler' invocation, while serializing component.
                }
                else
                {
                    // -- always reset existing listener
                    eventProperty.SetValue(Component, null);

                    // IEventBindingService removes existing listener method, if otherwise unattached.
                    // DesignEventSerializer will create no 'component.AddEventHandler' invocation, as there is no more CodeAttachEventStatement.
                }
            }
            finally
            {
                interceptedEventDescriptor = null;
                reEntrantCodeSet = false;
            }
        }

        #endregion

        #region IServiceProvider Members

        object IServiceProvider.GetService(Type serviceType)
        {
            return GetService(serviceType);
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions