Skip to main content
Solved

AddAbstractHandler null reference exeption

  • May 28, 2026
  • 1 reply
  • 24 views

Forum|alt.badge.img+2

Hi all. I have 
 public abstract class PMProjectMaterialsLineSplittingExtension<TGraph, TPrimary> : LineSplittingExtension<TGraph, TPrimary, PMProjectMaterials, PMProjectMaterialsSplit>
     where TGraph : PXGraph
     where TPrimary : class, IBqlTable, new()

 

public override void Initialize()
{
    base.Initialize();

    Base.RowSelected.AddAbstractHandler<TPrimary>(EventHandler);
    Base.RowUpdated.AddAbstractHandler<TPrimary>(EventHandler);

 PXCacheRights.Select);
}

When this Base.RowUpdated.AddAbstractHandler<TPrimary>(EventHandler); executed i have null reference exeption. I search code and find that proxy.InterceptedDelegate = null but default acumatica code try this:proxy.InterceptedDelegate.GetInvocationList().

I use PMProjectMaterialsLineSplittingExtension for ProjectEntry.
 public class LineSplittingExtension : PMProjectMaterialsLineSplittingExtension<ProjectEntry, PMProject> { }

I save that ProjectEntry not have RowUpdated method for PMProject. I think its problem.

Best answer by denisperez28

Hi,

I think the correct path here is to use AddHandler<T> instead of AddAbstractHandler<T>. About your suspicion that ProjectEntry does not have a RowUpdated method for PMProject: I do not think that is the issue. ProjectEntry is declared as:

public class ProjectEntry : PXGraph<ProjectEntry, PMProject>

So PMProject is the primary DAC, and the framework automatically exposes its RowUpdatedRowSelected, etc. events on the primary cache. A graph extension does not need the base graph to declare an explicit RowUpdated handler in order to subscribe to it. The extension can attach its own handler at any point, so the binding itself (TPrimary = PMProject) is correct.

The piece I would change is the API call. Your approach of subscribing programmatically inside Initialize() is right for a generic base extension where the primary DAC is a type parameter. Only the specific method needs to be different.

Your current code:

public abstract class PMProjectMaterialsLineSplittingExtension<TGraph, TPrimary>
: LineSplittingExtension<TGraph, TPrimary, PMProjectMaterials, PMProjectMaterialsSplit>
where TGraph : PXGraph
where TPrimary : class, IBqlTable, new()
{
public override void Initialize()
{
base.Initialize();

Base.RowSelected.AddAbstractHandler<TPrimary>(EventHandler);
Base.RowUpdated.AddAbstractHandler<TPrimary>(EventHandler);
}
}

public class LineSplittingExtension
: PMProjectMaterialsLineSplittingExtension<ProjectEntry, PMProject> { }

What I would change it to

public override void Initialize()
{
base.Initialize();

Base.RowSelected.AddHandler<TPrimary>(EventHandler);
Base.RowUpdated.AddHandler<TPrimary>(EventHandler);
}

private void EventHandler(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row is not TPrimary row) return;
// ... your logic
}

private void EventHandler(PXCache sender, PXRowUpdatedEventArgs e)
{
if (e.Row is not TPrimary row) return;
// ... your logic
}

When the concrete class binds TPrimary = PMProject, the subscription targets PMProject's RowUpdated and RowSelected on the project cache, which is what you want.

 

Why I think this is the right path

Acumatica itself uses this exact pattern in PX.Objects.FS\Extensions\FSSODetSplitPlan.cs, where a generic graph extension subscribes to the primary DAC's RowUpdated from Initialize() with AddHandler<T>:
 

public class FSSODetSplitPlan
: ItemPlan<ServiceOrderEntry, FSServiceOrder, FSSODetSplit>
{
public override void Initialize()
{
base.Initialize();

Base.FieldDefaulting.AddHandler<SiteStatusByCostCenter.negAvailQty>(
NegAvailQtyFieldDefaulting);

// resubscribe so this runs after EPApprovalAutomation
Base.RowUpdated.RemoveHandler<FSServiceOrder>(_);
Base.RowUpdated.AddHandler<FSServiceOrder>(_);
}

public override void _(Events.RowUpdated<FSServiceOrder> e)
{
base._(e);
// ...
}
}

And AddHandler<T> works with unresolved generic type parameters (equivalent to your TPrimary), as in PX.Objects.PR\Utility\CalculationResultInfo.cs:

public class CalculationResultInfo<TGraph, TTable> : IPXCustomInfo
where TGraph : PXGraph
where TTable : class, IBqlTable
{
public void Complete(PXLongRunStatus status, PXGraph graph)
{
if (graph is TGraph)
{
((TGraph)graph).RowSelected.AddHandler<TTable>((sender, e) =>
{
TTable row = e.Row as TTable;
if (row == null) return;
// ...
});
}
}
}

 

One more option, if the above does not behave as expected


The base class LineSplittingExtension<TGraph, TPrimary, TLine, TSplit> internally uses the ManualEvent API for its own subscriptions, and it is also generic-safe:
 

using PX.Data;

public override void Initialize()
{
base.Initialize();

ManualEvent.Row<TPrimary>.Selected.Subscribe(Base, EventHandler);
ManualEvent.Row<TPrimary>.Updated.Subscribe(Base, EventHandler);
}

private void EventHandler(ManualEvent.Row<TPrimary>.Selected.Args e)
{
// ... your logic
}

private void EventHandler(ManualEvent.Row<TPrimary>.Updated.Args e)
{
// e.Row, e.OldRow available
// ... your logic
}

Hope this helps!

Best,

1 reply

  • Jr Varsity I
  • Answer
  • May 28, 2026

Hi,

I think the correct path here is to use AddHandler<T> instead of AddAbstractHandler<T>. About your suspicion that ProjectEntry does not have a RowUpdated method for PMProject: I do not think that is the issue. ProjectEntry is declared as:

public class ProjectEntry : PXGraph<ProjectEntry, PMProject>

So PMProject is the primary DAC, and the framework automatically exposes its RowUpdatedRowSelected, etc. events on the primary cache. A graph extension does not need the base graph to declare an explicit RowUpdated handler in order to subscribe to it. The extension can attach its own handler at any point, so the binding itself (TPrimary = PMProject) is correct.

The piece I would change is the API call. Your approach of subscribing programmatically inside Initialize() is right for a generic base extension where the primary DAC is a type parameter. Only the specific method needs to be different.

Your current code:

public abstract class PMProjectMaterialsLineSplittingExtension<TGraph, TPrimary>
: LineSplittingExtension<TGraph, TPrimary, PMProjectMaterials, PMProjectMaterialsSplit>
where TGraph : PXGraph
where TPrimary : class, IBqlTable, new()
{
public override void Initialize()
{
base.Initialize();

Base.RowSelected.AddAbstractHandler<TPrimary>(EventHandler);
Base.RowUpdated.AddAbstractHandler<TPrimary>(EventHandler);
}
}

public class LineSplittingExtension
: PMProjectMaterialsLineSplittingExtension<ProjectEntry, PMProject> { }

What I would change it to

public override void Initialize()
{
base.Initialize();

Base.RowSelected.AddHandler<TPrimary>(EventHandler);
Base.RowUpdated.AddHandler<TPrimary>(EventHandler);
}

private void EventHandler(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row is not TPrimary row) return;
// ... your logic
}

private void EventHandler(PXCache sender, PXRowUpdatedEventArgs e)
{
if (e.Row is not TPrimary row) return;
// ... your logic
}

When the concrete class binds TPrimary = PMProject, the subscription targets PMProject's RowUpdated and RowSelected on the project cache, which is what you want.

 

Why I think this is the right path

Acumatica itself uses this exact pattern in PX.Objects.FS\Extensions\FSSODetSplitPlan.cs, where a generic graph extension subscribes to the primary DAC's RowUpdated from Initialize() with AddHandler<T>:
 

public class FSSODetSplitPlan
: ItemPlan<ServiceOrderEntry, FSServiceOrder, FSSODetSplit>
{
public override void Initialize()
{
base.Initialize();

Base.FieldDefaulting.AddHandler<SiteStatusByCostCenter.negAvailQty>(
NegAvailQtyFieldDefaulting);

// resubscribe so this runs after EPApprovalAutomation
Base.RowUpdated.RemoveHandler<FSServiceOrder>(_);
Base.RowUpdated.AddHandler<FSServiceOrder>(_);
}

public override void _(Events.RowUpdated<FSServiceOrder> e)
{
base._(e);
// ...
}
}

And AddHandler<T> works with unresolved generic type parameters (equivalent to your TPrimary), as in PX.Objects.PR\Utility\CalculationResultInfo.cs:

public class CalculationResultInfo<TGraph, TTable> : IPXCustomInfo
where TGraph : PXGraph
where TTable : class, IBqlTable
{
public void Complete(PXLongRunStatus status, PXGraph graph)
{
if (graph is TGraph)
{
((TGraph)graph).RowSelected.AddHandler<TTable>((sender, e) =>
{
TTable row = e.Row as TTable;
if (row == null) return;
// ...
});
}
}
}

 

One more option, if the above does not behave as expected


The base class LineSplittingExtension<TGraph, TPrimary, TLine, TSplit> internally uses the ManualEvent API for its own subscriptions, and it is also generic-safe:
 

using PX.Data;

public override void Initialize()
{
base.Initialize();

ManualEvent.Row<TPrimary>.Selected.Subscribe(Base, EventHandler);
ManualEvent.Row<TPrimary>.Updated.Subscribe(Base, EventHandler);
}

private void EventHandler(ManualEvent.Row<TPrimary>.Selected.Args e)
{
// ... your logic
}

private void EventHandler(ManualEvent.Row<TPrimary>.Updated.Args e)
{
// e.Row, e.OldRow available
// ... your logic
}

Hope this helps!

Best,