Already spoken of with @mh but now an official function request. This is needed to easily convert from VB.Net to Oxygene.
In contrast to C# and current Oxygene, VB does _not _ hard wire the event handlers in code (although it is possible to do this), but instead the wiring is compiler generated.
An example of the current event handler syntax:
type
MainForm = partial class(ExactUI.ExactMainForm)
private
method btnSluiten_Click(sender: System.Object; e: System.EventArgs);
assembly
btnSluiten:= System.Windows.Forms.Button;
public
constructor;
end;
implementation
constructor MainForm;
begin
InitializeComponent();
//Wire the event handler
self.btnSluiten.Click += new System.EventHandler(self.btnSluiten_Click);
end;
method MainForm.btnSluiten_Click(sender: System.Object; e: System.EventArgs);
begin
//click code here
end;
In VB style the would become:
type
MainForm = partial class(ExactUI.ExactMainForm)
private
//based on the handles modifier (that can have multiple events to implement them all on one method)
// the compiler will generate the wiring
method btnSluiten_Click(sender: System.Object; e: System.EventArgs); handles btnSluiten.Click;
assembly
//The ExposeEvents modifier exposes the events of this component and makes the use of Handles on it available
btnSluiten:= System.Windows.Forms.Button; ExposeEvents;
public
constructor;
end;
implementation
constructor MainForm;
begin
InitializeComponent();
//No event wiring needed; this is done on compiletime based on the used Handles modifiers
end;
method MainForm.btnSluiten_Click(sender: System.Object; e: System.EventArgs);
begin
//click code here
end;
The internal implementation of an Expose Event field is a property that removes all current event handlers and sets the new ones on the set method, the get method just returns the field.
The content of the set method is generated on compile time.
In this case the code after compile would be:
type
MainForm = partial class(ExactUI.ExactMainForm)
private
btnSluitenField:= System.Windows.Forms.Button;
method SetbntSluiten(value: System.Windows.Forms.Button);
method btnSluiten_Click(sender: System.Object; e: System.EventArgs)
assembly
property btnSluiten: System.Windows.Forms.Button; read btnSluitenField write SetbtnSluiten;
public
constructor;
end;
implementation
constructor MainForm;
begin
InitializeComponent();
//No event wiring needed; this is done on compiletime based on the used Handles modifiers
end;
method MainForm.SetbtnSluiten((value: System.Windows.Forms.Button));
begin
if btnSluitenField <> nil then
begin
btnSluitenField.Click.RemoveAll;
end;
btnSluitenField = value;
btnSluitenField.Click += new System.EventHandler(self.btnSluiten_Click);
end;
method MainForm.btnSluiten_Click(sender: System.Object; e: System.EventArgs);
begin
//click code here
end;
I just added the generated code to the initial post.
The power of this is that you can replace the instance, and the new instance is also wired immediately (and the old unwired).
You can also create an uninitialized variable with event handlers; they will be wired as soon as it is assigned.
9.6.2 WithEvents Variables
A type can declare that it handles some set of events raised by one of its instance or shared variables by declaring the instance or shared variable that raises the events with the WithEvents modifier. For example:
Class Raiser
Public Event E1()
Public Sub Raise()
RaiseEvent E1
End Sub
End Class
Module Test
Private WithEvents x As Raiser
Private Sub E1Handler() Handles x.E1
Console.WriteLine("Raised")
End Sub
Public Sub Main()
x = New Raiser()
End Sub
End Module
In this example, the method E1Handler handles the event E1 that is raised by the instance of the type Raiser stored in the instance variable x.
The WithEvents modifier causes the variable to be renamed with a leading underscore and replaced with a property of the same name that does the event hookup. For example, if the variable’s name is F, it is renamed to _F and a property F is implicitly declared. If there is a collision between the variable’s new name and another declaration, a compile-time error will be reported. Any attributes applied to the variable are carried over to the renamed variable.
The implicit property created by a WithEvents declaration takes care of hooking and unhooking the relevant event handlers. When a value is assigned to the variable, the property first calls the remove method for the event on the instance currently in the variable (unhooking the existing event handler, if any). Next the assignment is made, and the property calls the add method for the event on the new instance in the variable (hooking up the new event handler). The following code is equivalent to the code above for the standard module Test:
Module Test
Private _x As Raiser
Public Property x() As Raiser
Get
Return _x
End Get
Set (Value As Raiser)
' Unhook any existing handlers.
If _x IsNot Nothing Then
RemoveHandler _x.E1, AddressOf E1Handler
End If
' Change value.
_x = Value
' Hook-up new handlers.
If _x IsNot Nothing Then
AddHandler _x.E1, AddressOf E1Handler
End If
End Set
End Property
Sub E1Handler()
Console.WriteLine("Raised")
End Sub
Sub Main()
x = New Raiser()
End Sub
End Module
It is not valid to declare an instance or shared variable as WithEvents if the variable is typed as a structure.
In addition, WithEvents may not be specified in a structure, and WithEvents and ReadOnly cannot be combined.
If I understand it well, OAP can add code. In this case, code has to be changed; a field has to be converted to a property (the ExposeEvents modifier in my example), and the property must be implemented based on the handles modifier (in my example).
The aspect is here (this is in a separate dll, the compiler will remove the reference to that):
namespace ClassLibrary1;
uses
System.Linq,
RemObjects.Elements.Cirrus.*;
type
[AttributeUsage(AttributeTargets.Method)]
HandlesAspect = public class(Attribute, IMethodInterfaceDecorator)
private
fField, fEvent: String;
protected
public
constructor(aField, aEvent: String);
begin
fField := aField;
fEvent := aEvent;
end;
method HandleInterface(Services: IServices; aMethod: IMethodDefinition);
begin
var lMethod := aMethod.Owner.GetMethod('InitializeComponent', new IType[0], []) as IMethodDefinition;
if lMethod = nil then begin
Services.EmitError('InitializeComponent not found!');
exit;
end;
var lField := aMethod.Owner.GetField(fField) ;
if lField = nil then begin
Services.EmitError('Field '+fField+ ' not found!');
exit;
end;
var lType := lField.Type;
var lEvent: IEvent;
while lType <> nil do begin
lEvent := lType.GetEvents(fEvent).FirstOrDefault;
if lEvent <> nil then break;
lType := lType.ParentType;
end;
if lEvent = nil then begin
Services.EmitError('Event '+fEvent+ ' not found!');
exit;
end;
var lBeg := new BeginStatement(
new PlaceHolderStatement(),
new StandaloneStatement(new ProcValue(new IdentifierValue(fField), 'add_'+fEvent, new NewValue(lEvent.Type, new IdentifierValue(aMethod.Name)))));
lMethod.ReplaceMethodBody(lBeg);
end;
end;
end.
this just hooks up the event inside InitializeComponents, by calling add_click(new self.method1);
By this you mean I have to solve this myself?
If so, can you lead me to the place where I can learn about this AOP in Oxygene?
BTW: This is the only point left that VB can do and Oxygene not (except for XML literals, but I never used them).