[OPEN] [#128] Can problem of custom events/listeners needing new propery be overcome using Generics?

  1. #1

    [OPEN] [#128] Can problem of custom events/listeners needing new propery be overcome using Generics?

    Hi,

    I was reading this post: http://forums.ext.net/showthread.php?10401

    I was having the same problem as the original poster of that thread, about how to expose custom listeners on the C# side, as it is quite nice and easy on the ExtJs side.

    I saw these two comments, amongst others:

    Quote Originally Posted by Vladimir
    Yes, therefore we created Base classes without listeners and direct events. Because each new widget can has own events but we C# doesn't allow to override return type (interesting, as I remember Java allows override return type of methods which have different arguments)

    Well, I can suggest one workaround (just not sure if it is acceptable for you), you can add additional listeners property, like ExtnededListeners, and set serialization name as "listeners" (in this case, do not use standard listeners). As you see it is not very good solution but it can solve that problem

    Quote Originally Posted by geoffrey.mcgill View Post
    The reason we inherit Child Components from a common Base class is to work-around a defect in the ASP.NET runtime (and DesignTime?) parser.

    Parser Error Message: Ambiguous match found.
    Yep, thats the one.
    Looking at your code I was wondering, what if you used Generics to overcome this problem? In the case of TreePanel, I think it would mean something like this (untested and removed all the additional attributes just for clarity!):

    public partial class TreePanel<TListeners> : TreePanelBase, IAjaxPostBackEventHandler where TListeners : ComponentListeners, new()
    {
        ... all your other code here ...
    
        public TListeners Listeners
        {
            get
            {
                if (this.listeners == null)
                {
                    this.listeners = new TListeners();
                }
    
                return this.listeners;
            }
        }
    }
    I suspect the same could be done for DirectEvents too if people wanted to add their own custom ones. In that case you would do something like this for the class definition:

    public partial class TreePanel<TListeners, TDirectEvents> : TreePanelBase, IAjaxPostBackEventHandler
        where TListeners : ComponentListeners, new()
        where TDirectEvents : ComponentDirectEvents, new()
    {
       ... etc ...
    
       public TListeners Listeners { ... }
       
       ... etc ...
    
       public TDirectEvents DirectEvents { ... }
       
       ... etc ...
    }
    And the same could go for anything else.

    As it stands, the above would create new problems:
    1. How you could possibly use these in ASP.NET markup (due to syntax issues - a control can't be a generic itself I believe)
    2. Backward compatibility - even if this change is possible and desirable, it would break everyone.
    3. How could people extend the class

    These issues could be solved by doing the following steps:

    1. Renaming TreePanel to something like TreePanelProvider:

    public partial class TreePanelProvider<TListeners, TDirectEvents> : TreePanelBase, IAjaxPostBackEventHandler
        where TListeners : ComponentListeners, new()
        where TDirectEvents : ComponentDirectEvents, new()
    {
       ... etc ...
    
       public TListeners Listeners { ... }
       
       ... etc ...
    
       public TDirectEvents DirectEvents { ... }
       
       ... etc ...
    }
    2. Creating a new TreePanel class that does not use generics but can still be used by everyone, by simply having this:

    public class TreePanel : TreePanelProvider<TreePanelDirectListeners, TreePanelDirectEvents>
    {
        // note, no code needed here, as it is all moved into base class
        // this class just becomes a convenience for ASP.NET to be able to support markup
    }
    (If my assumption that a class with a generic such as Example<T> cannot directly be used as a server control is wrong, you could just have TreePanel itself be TreePanel<TreePanelDirectListeners, TreePanelDirectEvents).

    3. Finally, for someone to write a custom tree control they could simply do this:

    public class MyTreePanel : TreePanelProvider<MyCustomTreePanelDirectListeners, MyCustomTreePanelDirectEvents>
    {
        // note, no code needed here, as it is all moved into base class
        // this class just becomes a convenience for ASP.NET to be able to support markup
    }
    (If developers don't need custom events, they can continue extending TreePanel itself.)

    In effect, the above simply
    1. Pushes all the code in TreePanel into a new parent/base class
    2. Lets the new parent continue inheriting from the previous parent, thus not breaking functionality
    3. Allows the remaining TreePanel to be preserved as almost like a "marker" class that can continue to work with ASP.NET markup and therefore preserve backward compatibility where people are already extending it.
    4. Lets developers create custom tree panels with custom events if needed as shown by Example 3 above (which is minimal and same as Example 2, except it uses different listeners/events). In other words, both your "out of the box" tree panel and developers' custom ones inherit TreePanelProvider, if custom events are needed.
    5. Lets developers to continue inheriting and extending TreePanel itself if they don't need their own events


    I haven't looked at, or tried to modify my local copy of your source code to see if this works. Furthermore, I suspect it may be possible to somehow do this a lot further up the class hierarchy which means other panels could automatically also get the same benefits without them having to be duplicated into each class.

    Don't know if that is a possibility? (I can imagine that even if it is a good solution it is not necessarily something that you can easily refactor overnight, depending on how your autogenerated code stuff works.)
    Last edited by Daniil; Jan 18, 2013 at 4:23 AM. Reason: [OPEN] [#128]
  2. #2
    Hi @anup,

    Thanks for the investigation. We will look into it and comeback with an update.
  3. #3
    I think it's a great idea and I can confirm it works. Below is a working example based on your drafts.

    I guess we won't make such refactor for Ext.NET v1, but we will 100% consider to make it for v2.

    Though we have to investigate what possible problems might be there.

    Thanks again for the suggestion!

    ASPX page
    <%@ Page Language="C#" %>
    
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
    <%@ Register Assembly="Work" Namespace="Work" TagPrefix="cc" %>
    
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            MyTreePanel tree = new MyTreePanel()
            {
                Title = "Created in code behind",
                Root =
                {
                    new Ext.Net.TreeNode("Root")
                },
                Listeners =
                {
                    Click =
                    {
                        Fn = "onClick"
                    }
                }
            };
    
            tree.DirectEvents.Click.Event += MyTreePanel_Click;
    
            this.Form.Controls.Add(tree);
        }
    
        protected void MyTreePanel_Click(object sender, DirectEventArgs e)
        {
            X.Msg.Alert("Click", "Hello from Server!").Show();
        }
    </script>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Ext.NET Example</title>
    
        <script type="text/javascript">
            var onClick = function (node) {
                alert('Clicked ' + node.text + ' node.');    
            };
        </script>
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" />
            <cc:MyTreePanel runat="server" Title="Created in code behind">
                <Root>
                    <ext:TreeNode Text="Root" />
                </Root>
                <Listeners>
                    <Click Fn="onClick" />    
                </Listeners>
                <DirectEvents>
                    <Click OnEvent="MyTreePanel_Click" />
                </DirectEvents>
            </cc:MyTreePanel>
        </form>
    </body>
    </html>
    TreePanelProvider.cs
    using System.Web.UI;
    
    using Ext.Net;
    
    namespace Work
    {
        public class TreePanelProvider<TListeners, TDirectEvents> : TreePanelBase
            where TListeners : ComponentListeners, new()
            where TDirectEvents : ComponentDirectEvents, new()
        {
            private TListeners listeners;
            private TDirectEvents directEvents;
    
            public override string XType
            {
                get
                {
                    return "nettreepanel";
                }
            }
    
            public override string InstanceOf
            {
                get
                {
                    return "Ext.net.TreePanel";
                }
            }
    
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public TListeners Listeners
            {
                get
                {
                    if (this.listeners == null)
                    {
                        this.listeners = new TListeners();
                    }
    
                    return this.listeners;
                }
            }
    
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public TDirectEvents DirectEvents
            {
                get
                {
                    if (this.directEvents == null)
                    {
                        this.directEvents = new TDirectEvents();
                    }
    
                    return this.directEvents;
                }
            }
    
            public override ConfigOptionsCollection ConfigOptions
            {
                get
                {
                    ConfigOptionsCollection list = base.ConfigOptions;
    
                    list.Add("listeners", new ConfigOption("listeners", new SerializationOptions("listeners", JsonMode.Object), null, this.Listeners));
                    list.Add("directEvents", new ConfigOption("directEvents", new SerializationOptions("directEvents", JsonMode.Object), null, this.DirectEvents));
    
                    return list;
                }
            }
        }
    }
    MyTreePanel.cs
    using Ext.Net;
    
    namespace Work
    {
        public class MyTreePanel : TreePanelProvider<TreePanelListeners, TreePanelDirectEvents>
        {
    
        }
    }
  4. #4
    That's great Daniil. Glad it worked!

    I can imagine that most components in the hierarchy can benefit from this pattern in which case the use of generics would probably start much higher up the component hierarchy. As such, I can therefore accept that it is not easy to do in 1.x, as nice as that would be :)

    So, I could live with the limitation that in 1.x we will have to continue as is for now, which is to create a new property and not be able to use the old Listeners property and new one at the same time on the same instance.

    I would imagine that if this broke backward compatibility in 2.x it wouldn't be so bad - don't know how many people are doing this, and the beneficial change it may cause is perhaps worth it - also, for those not using custom listeners, they shouldn't notice any difference at all.
  5. #5
    Hi Anup,

    Apologize for a long delay.

    I have had a closer look at the suggestion.

    Now possible benefits don't look so obvious for me...

    Will it cause creating a new class for each component?

    I mean the following.

    1. Now it is:
    TreePanelBase => TreePanel

    2. Following your suggestion it will be:
    TreePanelBase => TreePanelProvider => TreePanel

    Do not I misunderstand something?

    What are exact benefits of the new design?

    Seems I see a single one. A developer won't need to implement the Listeners (and DirectEvents) property (though, it is, actually, just copying).

    1. Now it requires implementing:
    1.1. A MyTreePanelListeners class inheriting from the TreePanelListeners one
    1.2. A MyTreePanel class inheriting from the TreePanelBase one
    1.3. Its Listeners property using the MyTreePanelListeners class

    2. With the new design the step #1.3 won't be required.


    But, again, it causes creating new classes such as TreePanelProvider for each component. We have already rather a huge amount of classes. So, this aspect causes for concern.

    Anup, am I missing something?

    In addition the "Ambiguous match found" exception appears to be not reproducible in VS 2010 and VS 2012. Here is a test case.
    http://forums.ext.net/showthread.php...ll=1#post99240
    Last edited by Daniil; Dec 25, 2012 at 11:57 AM.
  6. #6
    Hi Daniil,

    Sorry for the delayed reply.

    It has been a while since I raised that original post, so I might struggle remembering everything just at this moment :)

    However, one of the benefits is to overcome the workaround Geoff described in this thread:
    http://forums.ext.net/showthread.php...some-questions (his post is the last one on the first page)

    My proposal perhaps introduces more classes to minimize backward compatability breakage.

    But it definitely isn't perfect.

    While I think you might be right in that it could introduce additional classes into your framework, it would be very small (for example, the TreePanel example in my first thread) is really a "marker" class to preserve backward compataibility.

    In addition, it may help minimize the amount of code developers have to right when introducing their own subclasses. (Because they don't have to implement each generic themselves; they can mix and match if they wanted).

    One additional drawback is that my example had a structure similar to Component<TListeners, TDirectEvents> but it is possible you will have a lot more, such as a generic for config options and any other collections that have to be overridden in this way.

    Also, it may help with unit testability because depending on what you are testing, you can provide mock implementations a bit more easily perhaps...

    If the benefits outweigh the number of classes required in the Ext.NET framework, it might be worth it. However, I also appreciate this could be a massive change that may not be easy at this point without being too disruptive for existing code, and that may not be something you want to handle...

    I will think about this further in coming days...
  7. #7
    Thank you for the answer.

    Quote Originally Posted by anup View Post
    It has been a while since I raised that original post, so I might struggle remembering everything just at this moment :)
    Apologize for that. I understand you. It was also a great effort for me to recall details:)

    Quote Originally Posted by anup View Post
    However, one of the benefits is to overcome the workaround Geoff described in this thread:
    http://forums.ext.net/showthread.php...some-questions (his post is the last one on the first page)
    As I mentioned
    Quote Originally Posted by Daniil View Post
    In addition the "Ambiguous match found" exception appears to be not reproducible in VS 2010 and VS 2012. Here is a test case.
    http://forums.ext.net/showthread.php...ll=1#post99240
    the issue is not reproducible in VS 2010 and 2012.

    So, we could also refuse from our "Base" classes convention (after dropping VS 2008). It would also extremely reduce the amount of classes.

    But either way - following your suggestion to use generics (which is definitely excellent) or dropping "Base" classes - is a tremendous change. It would require a lot of re-coding, testing. Moreover, unpredictable issues are possible.

    So, I don't think it is possible (unfortunately) to apply it at this point.

    But I created an Issue to have a possibility to review it in future. Also I will keep it in my mind.
    https://github.com/extnet/Ext.NET/issues/128

Similar Threads

  1. [CLOSED] Adding events to custom control
    By jmcantrell in forum 1.x Legacy Premium Help
    Replies: 6
    Last Post: Mar 29, 2012, 8:16 PM
  2. .Net Generics in DirectMethod / DirectEvent
    By bright in forum 1.x Help
    Replies: 1
    Last Post: Mar 22, 2012, 11:01 AM
  3. Replies: 1
    Last Post: May 13, 2011, 12:05 PM
  4. Replies: 4
    Last Post: Dec 10, 2010, 2:55 PM
  5. [CLOSED] Adding Custom Events
    By conman in forum 1.x Legacy Premium Help
    Replies: 5
    Last Post: Sep 04, 2009, 8:50 PM

Posting Permissions