PDA

View Full Version : [CLOSED] Extending a base component in order to provide custom Direct Events - some questions



anup
Aug 06, 2012, 9:31 PM
Hi,

Need some advice here. For the book I am turning my attention to the chapter on custom components.

In my own experience, this is really easy to do once you get used to it and the general steps are mentioned in quite a few forum posts (about xtype/instanceof, embedded resources, custom listeners, etc etc).

All good.

When it comes to custom events, in my own components at work I've not needed to create Direct Event counterparts as we tend to use Listeners or Direct Methods mostly. So I've generally followed the pattern described in here, for example: http://forums.ext.net/showthread.php?10401

However, for the book, I thought it makes sense that for completeness, if a component has custom listeners, then it should have corresponding custom Direct Events. Looking at the source code and forums, a similar pattern to listeners can be followed for Direct Events, too.

So I tried it out. I found that in my example where I extend GridPanel with custom listener it is fine. But when I try to add a custom Direct Event in the same way, then it does not work; I found that to add custom Direct Events I must extend GridPanelBase, not GridPanel

And this is explained nicely, for example here:

http://forums.ext.net/showthread.php?11718-CLOSED-Creating-custom-control

and here:

http://forums.ext.net/showthread.php?18103-CLOSED-Adding-events-to-custom-control (http://forums.ext.net/showthread.php?18103-CLOSED-Adding-events-to-custom-control)

This is not a problem itself - in fact, one benefit is that by extending GridPanelBase, I get to call my custom listeners property Listeners and my custom DirectEvents property DirectEvents instead of having to use some other names. This makes it much easier for a user of the component to use it.

However, as noted in the forum post 11718, we need to ensure final classes don't have important methods/properties; they should be moved to the base class:



Hi,



So for future reference, should I not inherit from a final class? What if I want to enhance a combobox with more listeners and directevents?


If you don't need to add own events then you can inherit from final class



What if I want to enhance a combobox with more listeners and directevents?


There is base class for combo, you have to inherit from that class (just we should ensure that final class doesn't have important methods or properties (move all code to base class))


That is fair enough and makes sense.

Unfortunately, looking at TreePanel, there are a number of methods in there that look important (I could be wrong), so inheriting TreePanelBase to add my own custom Direct Events means I risk losing some useful functionality maybe. For TabPanel, the base class is AbstractTabPanel (there isn't a TabPanelBase) and it too looks to have functionality in TabPanel which I might lose. TextField also has some additional things in there, etc etc. There are so many controls (which is good!) that I can't possibly check them all; nor can you. You would have to wait for people to let you know and let them discover the problem first, I guess?

So question is what to do - especially from a book perspective; I risk making this important topic really complex to follow. I was going to just settle on saying extend the various *base* classes rather than a final class, but now I am not sure.

I could continue to focus on custom listeners and add a note about refactoring that might be needed to support DirectEvents and say that some classes may need to extend the base class but in other cases that cannot be done or functionality in a final class needs to be copied (is that right?) to your extension of a base class, and maybe point to this forum post for more information. If you find most people only add custom listeners and not direct events, then I could do that. I'm just not sure if that is right from a book point of view...

Any thoughts? Hopefully I've misunderstood something and there is a very easy answer :)

Daniil
Aug 07, 2012, 4:45 AM
Hi Anup,



I found that in my example where I extend GridPanel with custom listener it is fine. But when I try to add a custom Direct Event in the same way, then it does not work;

It appears to be working for me for DirectEvents as well. Here is an example (without client part since we are now interested in server part only).

MyTextFieldListeners, MyTextFieldDirectEvents, MyTextField classes

using System.ComponentModel;
using System.Web.UI;
using System.Xml.Serialization;
using Newtonsoft.Json;
using Ext.Net;

namespace Work2
{
public class MyTextFieldListeners : TextFieldListeners
{

private ComponentListener someListener;


[ListenerArgument(0, "item", typeof(Field), "This text field")]
[TypeConverter(typeof(ExpandableObjectConverter))]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public virtual ComponentListener SomeListener
{
get
{
return this.someListener ?? (this.someListener = new ComponentListener());
}
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add("someListener", new ConfigOption("someListener", new SerializationOptions("someListener", typeof(ListenerJsonConverter)), null, this.SomeListener));

return list;
}
}
}

public class MyTextFieldDirectEvents : TextFieldDirectEvents
{
public MyTextFieldDirectEvents(Observable parent) { this.Parent = parent; }

private ComponentDirectEvent someDirectEvent;


[ListenerArgument(0, "item", typeof(Field), "This text field")]
[TypeConverter(typeof(ExpandableObjectConverter))]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public virtual ComponentDirectEvent SomeDirectEvent
{
get
{
return this.someDirectEvent ?? (this.someDirectEvent = new ComponentDirectEvent(this));
}
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add("someDirectEvent", new ConfigOption("someDirectEvent", new SerializationOptions("someDirectEvent", typeof(DirectEventJsonConverter)), null, this.SomeDirectEvent));

return list;
}
}
}

public class MyTextField : TextField
{
private MyTextFieldListeners listeners;

[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
public MyTextFieldListeners MyListeners
{
get
{
if (this.listeners == null)
{
this.listeners = new MyTextFieldListeners();
}

return this.listeners;
}
}

private MyTextFieldDirectEvents directEvents;


[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
public MyTextFieldDirectEvents MyDirectEvents
{
get
{
if (this.directEvents == null)
{
this.directEvents = new MyTextFieldDirectEvents(this);
}

return this.directEvents;
}
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add("listeners", new ConfigOption("listeners", new SerializationOptions("listeners", JsonMode.Object), null, this.MyListeners));
list.Add("directEvents", new ConfigOption("directEvents", new SerializationOptions("directEvents", JsonMode.Object), null, this.MyDirectEvents));

return list;
}
}
}
}

Example Page

<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Register Assembly="Work2" Namespace="Work2" TagPrefix="cc" %>

<script runat="server">
protected void TestDirectEventHandler(object sender, DirectEventArgs e)
{

}
</script>

<!DOCTYPE html>

<html>
<head runat="server">
<title>Ext.NET v2 Example</title>
</head>
<body>
<ext:ResourceManager runat="server" />

<cc:MyTextField runat="server">
<MyListeners>
<SomeListener Handler="alert('Hello!');" />
</MyListeners>
<MyDirectEvents>
<SomeDirectEvent OnEvent="TestDirectEventHandler" />
</MyDirectEvents>
</cc:MyTextField>
</body>
</html>

It produces the following JavaScript.

Resulting JavaScript


Ext.create("Ext.form.field.Text", {
id: "ctl04",
renderTo: "App.ctl04_Container",
listeners: {
someListener: {
fn: function (item) {
alert('Hello!');
}
}
},
directEvents: {
someDirectEvent: {
fn: function (item) {
Ext.net.directRequest({
control: this,
action: 'SomeDirectEvent'
});
}
}
}
});


So, there are the expected "someListener" and "someDirectEvent".




Unfortunately, looking at TreePanel, there are a number of methods in there that look important (I could be wrong), so inheriting TreePanelBase to add my own custom Direct Events means I risk losing some useful functionality maybe. For TabPanel, the base class is AbstractTabPanel (there isn't a TabPanelBase) and it too looks to have functionality in TabPanel which I might lose. TextField also has some additional things in there, etc etc. There are so many controls (which is good!) that I can't possibly check them all; nor can you. You would have to wait for people to let you know and let them discover the problem first, I guess?

Agree, there is a problem. Inhereting from a "base" class will cause losing some functionality of a "final" class. Honestly, I am not sure why where some functionality in "final" classes apart from XType, InstanceOf, Listeners and DirectEvents. I will discuss it with Vladimir and come back to you.

anup
Aug 07, 2012, 9:20 AM
Thanks for the details Daniil,


Hi Anup,

It appears to be working for me for DirectEvents as well. Here is an example (without cliend part since we are now interested in server part only).

MyTextFieldListeners, MyTextFieldDirectEvents, MyTextField classes


Maybe it is fine for TextField in your example, but I was getting errors when using GridPanel. Maybe it is specific issues for different controls. Maybe later today when I get a moment I will send the Grid example that I had errors with.



Agree, there is a problem. Inhereting from a "base" class will cause losing some functionality of a "final" class. Honestly, I am not sure why where some functionality in "final" classes apart from XType, InstanceOf, Listeners and DirectEvents. I will discuss it with Vladimir and come back to you.

Ok. Thanks. I guess my main challenge is how to explain this in a book without confusing or scaring readers, because once you overcome this, the mechanisms you provide for custom components are really nice and generally simple and very powerful.

Daniil
Aug 07, 2012, 12:09 PM
I have discussed the issue with Vladimir.

There should not be anything in a "final" class apart from XType, InstanceOf, Listeners and DirectEvents. And you can follow that statement in the book.

So, you have just discovered the issues in Ext.NET. I mean this:


Unfortunately, looking at TreePanel, there are a number of methods in there that look important (I could be wrong), so inheriting TreePanelBase to add my own custom Direct Events means I risk losing some useful functionality maybe. For TabPanel, the base class is AbstractTabPanel (there isn't a TabPanelBase) and it too looks to have functionality in TabPanel which I might lose. TextField also has some additional things in there, etc etc

They just should be fixed. Thanks for the reports.



Maybe it is fine for TextField in your example, but I was getting errors when using GridPanel. Maybe it is specific issues for different controls. Maybe later today when I get a moment I will send the Grid example that I had errors with.

We will do our best to investigate a sample.

anup
Aug 07, 2012, 1:49 PM
If it helps, here is a custom grid - it is a bit long but I show 2 versions:
1) A custom grid that extends GridPanelBase (works)
2) The same custom grid but extending GridPanel instead (works only if you do not use DirectEvents. If you do, you get an exception - stack trace further below)

For simplicity I've put the client side JS into the test ASPX which I will show first:



<%@ Page Language="C#" %>
<%@ Register TagPrefix="cc1" Namespace="Ext.Net2.Tests.Grids.DirectEvent" Assembly="Ext.Net2.Tests" %>

<script runat="server">
protected void CustomGrid1_CompanySelected(object sender, DirectEventArgs e)
{
X.Msg.Alert("From Base class: CompanySelected", e.ToString()).Show();
}

protected void CustomGridFromFinalClass1_CompanySelected(object sender, DirectEventArgs e)
{
X.Msg.Alert("From Final class: CompanySelected", e.ToString()).Show();
}

protected void Page_Load(object sender, EventArgs e)
{
if (!X.IsAjaxRequest)
{
var data = CompanyData.GetData();

CustomGrid1.Store[0].DataSource = data;
CustomGrid1.Store[0].DataBind();


CustomGridFromFinalClass1.Store[0].DataSource = data;
CustomGridFromFinalClass1.Store[0].DataBind();
}
}
</script>

<!DOCTYPE html>
<html>
<head runat="server">
<title>Simple Array Grid - Ext.NET Examples</title>
<style type="text/css">
body { padding: 10px; }
.x-grid-row-over .x-grid-cell-inner {
font-weight : bold;
}
.positive { color: green; }
.negative { color: red; }
</style>

<ext:ResourcePlaceHolder Mode="Script" runat="server" />

<script type="text/javascript">
Ext.define('MyApp.CustomGrid', {
extend: 'Ext.grid.Panel',

alias: 'customgrid',

template: '<span class="{0}">{1}</span>',

change: function(value) {
return Ext.String.format(this.template, (value > 0) ? "positive" : "negative", value);
},

pctChange: function(value) {
return Ext.String.format(this.template, (value > 0) ? "positive" : "negative", value + "%");
},

initEvents: function() {
this.addEvents('companyselected');

this.on('select', this.onSelect, this);

this.callParent();
},

onSelect: function(row, record) {
this.fireEvent('companyselected', this, record);
}
});
</script>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" />

<h1>Grid built by extending the base class (works)</h1>

<cc1:CustomGrid Id="CustomGrid1" runat="server">
<Listeners>
<CompanySelected Handler="console.log(this, arguments)" />
</Listeners>
<DirectEvents>
<CompanySelected OnEvent="CustomGrid1_CompanySelected" />
</DirectEvents>
</cc1:CustomGrid>

<h1>Grid built by extending the final class (does not work - works if you do not implement direct events)</h1>

<cc1:CustomGridFromFinalClass Id="CustomGridFromFinalClass1" runat="server">
<CustomGridListeners>
<CompanySelected Handler="console.log(this, arguments)" />
</CustomGridListeners>
<CustomGridDirectEvents>
<CompanySelected OnEvent="CustomGridFromFinalClass1_CompanySelected" />
</CustomGridDirectEvents>
</cc1:CustomGridFromFinalClass>
</form>
</body>
</html>


Now the events that both grid controls use:

Custom Listener



public class CustomGridListeners : GridPanelListeners
{
private const string CompanySelectedJs = "companyselected";

private ComponentListener _companySelected;


[ListenerArgument(0, "grid")]

[ListenerArgument(1, "record")]
[TypeConverter(typeof(ExpandableObjectConverter))]
[ConfigOption(CompanySelectedJs, typeof(ListenerJsonConverter))]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public virtual ComponentListener CompanySelected
{
get { return _companySelected ?? (_companySelected = new ComponentListener()); }
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add(CompanySelectedJs,
new ConfigOption("CompanySelected",
new SerializationOptions(CompanySelectedJs,
typeof(ListenerJsonConverter)),
null,
CompanySelected));

return list;
}
}
}


Custom direct event:



public class CustomGridDirectEvents : GridPanelDirectEvents
{
public CustomGridDirectEvents() { }

public CustomGridDirectEvents(Observable parent)
{
Parent = parent;
}

private ComponentDirectEvent _companySelected;


[ListenerArgument(0, "grid", typeof(CustomGrid))]

[ListenerArgument(1, "record", typeof(Model))]
[TypeConverter(typeof(ExpandableObjectConverter))]
[ConfigOption("companyselected", typeof(DirectEventJsonConverter))]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public virtual ComponentDirectEvent CompanySelected
{
get
{
return _companySelected ?? (_companySelected = new ComponentDirectEvent(this));
}
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;


list.Add("companySelected",
new ConfigOption("CompanySelected",
new SerializationOptions("companyselected",
typeof(DirectEventJsonConverter)),
null,
CompanySelected));


return list;
}
}
}


Now, the implementations. First the GridPanelBase extension (which works)



public class CustomGrid : GridPanelBase
{
public override string InstanceOf
{
get { return "MyApp.CustomGrid"; }
}

public override string XType
{
get { return "customgrid"; }
}

private CustomGridListeners _listeners;

[Meta]
[ConfigOption("listeners", JsonMode.Object)]
[Category("2. Observable")]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
public CustomGridListeners Listeners
{
get { return _listeners ?? (_listeners = new CustomGridListeners()); }
}

private CustomGridDirectEvents _directEvents;

[Meta]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
[ConfigOption("directEvents", JsonMode.Object)]
public CustomGridDirectEvents DirectEvents
{
get { return _directEvents ?? (_directEvents = new CustomGridDirectEvents(this)); }
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add("listeners", new ConfigOption("Listeners", new SerializationOptions("listeners", JsonMode.Object), null, Listeners));
list.Add("directEvents", new ConfigOption("DirectEvents", new SerializationOptions("directEvents", JsonMode.Object), null, DirectEvents));

return list;
}
}

protected override void OnInit(EventArgs e)
{
Title = string.IsNullOrWhiteSpace(Title) ? "Simple Grid" : Title;
Store.Add(BuildStore());
ColumnModel.Columns.Add(BuildColumnModel());
SelectionModel.Add(new RowSelectionModel());
BottomBar.Add(new PagingToolbar());

base.OnInit(e);
}

private Store BuildStore()
{
return new Store
{
PageSize = 10,
Model =
{
new Model
{
Fields =
{
new ModelField("Company") { Mapping = "Name" },
new ModelField("Price", ModelFieldType.Float),
new ModelField("Change", ModelFieldType.Float),
new ModelField("PctChange", ModelFieldType.Float) { Mapping="PercentChange" },
new ModelField("LastChange", ModelFieldType.Date, "yyyy-MM-ddTHH:mm:ss"),
new ModelField("Symbol")
}
}
}
};
}

private IEnumerable<ColumnBase> BuildColumnModel()
{
return new ItemsCollection<ColumnBase>
{
new RowNumbererColumn { Width=25 },
new Column { Text="Company", DataIndex="Company", Flex=1 },
new Column
{
Text = "Price",
DataIndex = "Price",
Width = 50,
Renderer = { Format = RendererFormat.UsMoney }
},
new Column { Text="Change", DataIndex="Change", Width=50, Renderer = { Handler = "return this.change.apply(this, arguments);" } },
new Column { Text="Change", DataIndex="PctChange", Width=50, Renderer = { Handler = "return this.pctChange.apply(this, arguments);" } },
new DateColumn { Text = "Last Updated", DataIndex = "LastChange", Format="yyyy-MM-dd hh:mmtt", Width=130 }
};
}
}


And finally, the extension from the final class



public class CustomGridFromFinalClass : GridPanel
{
public override string InstanceOf
{
get { return "MyApp.CustomGrid"; }
}

public override string XType
{
get { return "customgrid"; }
}

private CustomGridListeners _customGridListeners;

[Meta]
[ConfigOption("listeners", JsonMode.Object)]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
public CustomGridListeners CustomGridListeners
{
get { return _customGridListeners ?? (_customGridListeners = new CustomGridListeners()); }
}

private CustomGridDirectEvents _customGridDirectEvents;

[Meta]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
[ConfigOption("directEvents", JsonMode.Object)]
public CustomGridDirectEvents CustomGridDirectEvents
{
get { return _customGridDirectEvents ?? (_customGridDirectEvents = new CustomGridDirectEvents(this)); }
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;


list.Add("customGridListeners", new ConfigOption("CustomGridListeners", new SerializationOptions("listeners", JsonMode.Object), null, CustomGridListeners));
list.Add("customGridDirectEvents", new ConfigOption("CustomGridDirectEvents", new SerializationOptions("directEvents", JsonMode.Object), null, CustomGridDirectEvents));


return list;
}
}

protected override void OnInit(EventArgs e)
{
Title = string.IsNullOrWhiteSpace(Title) ? "Simple Grid" : Title;
Store.Add(BuildStore());
ColumnModel.Columns.Add(BuildColumnModel());
SelectionModel.Add(new RowSelectionModel());
BottomBar.Add(new PagingToolbar());

base.OnInit(e);
}

private Store BuildStore()
{
return new Store
{
PageSize = 10,
Model =
{
new Model
{
Fields =
{
new ModelField("Company") { Mapping = "Name" },
new ModelField("Price", ModelFieldType.Float),
new ModelField("Change", ModelFieldType.Float),
new ModelField("PctChange", ModelFieldType.Float) { Mapping="PercentChange" },
new ModelField("LastChange", ModelFieldType.Date, "yyyy-MM-ddTHH:mm:ss"),
new ModelField("Symbol")
}
}
}
};
}

private IEnumerable<ColumnBase> BuildColumnModel()
{
return new ItemsCollection<ColumnBase>
{
new RowNumbererColumn { Width=25 },
new Column { Text="Company", DataIndex="Company", Flex=1 },
new Column
{
Text = "Price",
DataIndex = "Price",
Width = 50,
Renderer = { Format = RendererFormat.UsMoney }
},
new Column { Text="Change", DataIndex="Change", Width=50, Renderer = { Handler = "return this.change.apply(this, arguments);" } },
new Column { Text="Change", DataIndex="PctChange", Width=50, Renderer = { Handler = "return this.pctChange.apply(this, arguments);" } },
new DateColumn { Text = "Last Updated", DataIndex = "LastChange", Format="yyyy-MM-dd hh:mmtt", Width=130 }
};
}
}


Here is the stack trace I get when clicking on a row in the second (final class extension) grid:


Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

Line 818: PropertyInfo eventListenerInfo = directevents.GetType().GetProperty(eventName);
Line 819:
Line 820: if (eventListenerInfo.PropertyType != typeof(ComponentDirectEvent))
Line 821: {
Line 822: throw new HttpException("The control '{1}' does not have an DirectEvent with the name '{0}'".FormatWith(eventName, this.ClientID));

Source File: D:\Ext.Net\2.1\Ext.Net\Ext\Util\Observable.cs Line: 820

Stack Trace:

[NullReferenceException: Object reference not set to an instance of an object.]
Ext.Net.Observable.FireAsyncEvent(String eventName, ParameterCollection extraParams) in D:\Ext.Net\2.1\Ext.Net\Ext\Util\Observable.cs:820
Ext.Net.ResourceManager.RaisePostBackEvent(String eventArgument) in D:\Ext.Net\2.1\Ext.Net\Core\ResourceManager\Resour ceManager.cs:3208
System.Web.UI.Page.RaisePostBackEvent(IPostBackEve ntHandler sourceControl, String eventArgument) +13
System.Web.UI.Page.RaisePostBackEvent(NameValueCol lection postData) +176
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5563


Extending the *base* class instead of the final class is nicer because you can get to use the familiar Listeners and DirectEvents properties instead of custom names so if the fix involves a lot of effort, I in particular am not too worried about it given that we would advise to extend a "base" rather than "final" class, anyway.

Hope that helps.

Vladimir
Aug 07, 2012, 3:58 PM
About exception, Update from SVN
and the following code to inheritor class (which is inherited from final class)


protected override string DirectEventsKey
{
get
{
return "CustomGridDirectEvents";
}
}

Daniil
Aug 07, 2012, 4:09 PM
Extending the *base* class instead of the final class is nicer because you can get to use the familiar Listeners and DirectEvents properties instead of custom names so if the fix involves a lot of effort, I in particular am not too worried about it given that we would advise to extend a "base" rather than "final" class, anyway.


Absolutely, it's better to inherit from a base class.

We just consider a case with final classes as a possible solution for some specific scenarios (though, not sure what exactly:) ). Or, maybe, some developer could just like it more. And as a demonstration of Ext.NET flexibility:)

Vladimir
Aug 07, 2012, 4:26 PM
As I remember VS 2008 had a bug if shadow property


"Ambiguous match found" Exception thrown in VS if Parent class defines Listeners property.
REFERENCE: http://en.csharp-online.net/CSharp_Coding_Solutions%E2%80%94Understanding_the_ Overloaded_Return_Type_and_Property


May be the issue is solved in VS 2010. Try shadow Listeners and DirectEvents


public class CustomGridFromFinalClass : GridPanel
{
....

private CustomGridListeners _customGridListeners;

[Meta]
[ConfigOption("listeners", JsonMode.Object)]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
new public CustomGridListeners Listeners
{
get { return _customGridListeners ?? (_customGridListeners = new CustomGridListeners()); }
}

private CustomGridDirectEvents _customGridDirectEvents;

[Meta]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
[ConfigOption("directEvents", JsonMode.Object)]
new public CustomGridDirectEvents DirectEvents
{
get { return _customGridDirectEvents ?? (_customGridDirectEvents = new CustomGridDirectEvents(this)); }
}

....
}

anup
Aug 07, 2012, 5:04 PM
Thanks Vladimir, that worked.

If you agree with the following summary (to make sure I understood right) then you could close this thread:

When extending components you can extend the base or final class based on your scenarios:

If not extending any events (just custom code, CSS, JavaScript, etc) then extending final class is fine (you get the default listeners/direct events for free)
If extending direct events and/or listeners then:

Extending final class usually works but you have to create a new property for your custom listeners and direct events.
To avoid creating custom listener/direct event properties, extending base class is preferred

geoffrey.mcgill
Aug 07, 2012, 6:11 PM
Thanks Vladimir, that worked.

If you agree with the following summary (to make sure I understood right) then you could close this thread:

When extending components you can extend the base or final class based on your scenarios:

If not extending any events (just custom code, CSS, JavaScript, etc) then extending final class is fine (you get the default listeners/direct events for free)
If extending direct events and/or listeners then:

Extending final class usually works but you have to create a new property for your custom listeners and direct events.
To avoid creating custom listener/direct event properties, extending base class is preferred




Your statements are correct.

I'll just add a few comments (most of which has already been stated) to help clarify the history of our inheritance structure decisions.

As noted by Vladimir above, back in Visual Studio 2008, there was an ASP.NET Parser defect (submitted and confirmed with Microsoft):


"The parser cannot handle a derived control that hides a property on its base class, when the properties have different types, and the property on the derived control is a complex type."

The Parser threw an "Ambiguous match found" Exception. No solution was provided by Microsoft to solve and/or work-around this problem.

We did find a work-around, which was to create dedicated "Base" classes for each Control, none of which contained either the DirectEvents or Listeners properties. Those two properties could only be added to the Child/Leaf classes, which would then never have to override any Parent property. The Child classes are then just very basic inheritors of their Parent "Base" class, with a couple simple property type overrides (XType, InstanceOf, etc) and custom DirectEvent and Listener properties.

In almost all cases, the parent "Base" classes should contain all the server-side logic (Properties, Methods, Event Handling, etc). The Child classes, which are the actual ASP.NET Controls, are "Dumb" classes. They shouldn't contain any extra Properties or Methods.

If a Child "Control" class does contain extra Properties and/or Methods not found in the Parent, then we've [probably] made a mistake and these elements should be moved up to the Parent. There may be instances where this rule does not (or cannot apply), although I can't think of any off the top of my head (Vladimir knows these cases better than me). We'll have to spend some time reviewing all the classes and make adjustments as required.

This "Ambiguous match found" Exception may have been solved in VS 2010, although I suspect not. I believe the Exception was only thrown in VS Design Mode, which we've had to scale back support for because of other VS defects too.

Hope this helps.

anup
Aug 07, 2012, 9:51 PM
To all three of you - thanks for that extra info :)

I think I will probably link to this thread from the book chapter as further info / background!

By the way, about overcoming the parser issues - the base class approach makes sense and is good. A year back I had a thought about using Generics to overcome this problem. Daniil had a quick look at my suggestion and it seemed to be a possibility (though I can understand it is not trivial to put in, if desirable). That thread is here FYI:
http://forums.ext.net/showthread.php?16764-Can-problem-of-custom-events-listeners-needing-new-propery-be-overcome-using-Generics

(Vladimir, when I replied to say "it" worked, I meant your first response about the fix for Direct Events. While I was replying I hadn't seen your other post about shadow properties. I will try that some time tomorrow when I am back at my desk.)

geoffrey.mcgill
Aug 07, 2012, 10:08 PM
By the way, about overcoming the parser issues - the base class approach makes sense and is good. A year back I had a thought about using Generics to overcome this problem. Daniil had a quick look at my suggestion and it seemed to be a possibility (though I can understand it is not trivial to put in, if desirable). That thread is hear FYI:
http://forums.ext.net/showthread.php?16764-Can-problem-of-custom-events-listeners-needing-new-propery-be-overcome-using-Generics

Thanks for linking to your solution. It is a good one.

I'll do some testing tonight with this new class structure. I still have the original "Ambiguous match found" defect report Project sent to Microsoft, so I'll test against that as well.

Daniil
Dec 25, 2012, 11:55 AM
I'll do some testing tonight with this new class structure.

I made an update in that thread.
http://forums.ext.net/showthread.php?16764&p=99239&viewfull=1#post99239



I still have the original "Ambiguous match found" defect report Project sent to Microsoft, so I'll test against that as well.

I tested it. The issue is no longer reproducible in VS 2010 and VS 2012.

Here is my test case (it reproduces the issue in VS 2008).

Example Class

using System.ComponentModel;
using System.Web.UI;
using System.Xml.Serialization;
using Newtonsoft.Json;
using Ext.Net;

namespace Work2
{
public class MyTextFieldListeners : TextFieldListeners
{
private ComponentListener someListener;


[ListenerArgument(0, "item", typeof(Field), "This text field")]
[TypeConverter(typeof(ExpandableObjectConverter))]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public virtual ComponentListener SomeListener
{
get
{
return this.someListener ?? (this.someListener = new ComponentListener());
}
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;

list.Add("someListener", new ConfigOption("someListener", new SerializationOptions("someListener", typeof(ListenerJsonConverter)), null, this.SomeListener));

return list;
}
}
}

public class MyTextField : TextField
{
private MyTextFieldListeners listeners;

[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializat ionVisibility.Visible)]
new public MyTextFieldListeners Listeners
{
get
{
if (this.listeners == null)
{
this.listeners = new MyTextFieldListeners();
}

return this.listeners;
}
}
}
}

Example Page

<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Register Assembly="Work2" Namespace="Work2" TagPrefix="cc" %>

<!DOCTYPE html>

<html>
<head runat="server">
<title>Ext.NET v2 Example</title>
</head>
<body>
<ext:ResourceManager runat="server" />

<cc:MyTextField runat="server">
<Listeners>
<SomeListener Handler="alert('Hello!');" />
</Listeners>
</cc:MyTextField>
</body>
</html>