This article will explain how to build a COM visual component in C# using Visual Studio 2010; it is an extension of my earlier example of using the Interop Forms Toolkit to build a Visual Basic COM object.
First, you need to install the Microsoft Interop Toolkit (available here). Then, download the C# Interop Form Toolkit by Leon Langleyben from CodeProject. Installation of this is primarily copying files to the appropriate places based on your Visual Studio install (instructions are provided in Leon’s article).
Begin by creating a new project (Visual C#) with the ‘VB6 Interop UserControl’ Template.
Give the project a meaningful name, ‘IOPCsharpexample
’ is my choice.
Visual Studio creates the Solution with a variety of files/references. All of the code will be done in the InteropUserControl.cs file.
Opening the InteropUserControl.cs file (right click on the item in the Solution Explorer and choose ‘View Code’) gives this:
Clicking on the plus to the left of ‘Interfaces’ expands that code section:
Note that there are two interface definitions, one for events and another for properties (and methods). This is one place we need to add to in order to expose the control within Powerscript.
To build the actual control, open the Designer window for the InteropUserControl.cs file (right click and choose ‘View Designer’).
This gives you the layout of the user control itself:
Add a trackbar control from the Tools window:
Change some of the trackbar attributes in the Properties display:
The changes from the default are Name – TB1, Backcolor – Yellow, Orientation – Vertical, TickFrequency – 10, TickStyle – Both, Maximum – 100, and Margin – 0,0,0,0. Unlike the VB control I demonstrated before, this is a vertical trackbar without any label to display its current value.
On the properties view for the trackbar TB1, click on the Events button. This shows a list of the defined events for the control. Double click on the ValueChanged
event. This will add a stub for the event on the code page.
However, we must also add an initialization for an event handler to the event within the objects declaration so that it will be exposed.
Use the same steps to expose the Scroll
event on the trackbar. Once finished, you can see the events created in bold on the Properties window.
Now to complete the C# code, refer to the following:
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.VisualBasic;
using System.Windows.Forms;
using System.Security.Permissions;
using System.Drawing;
namespace IOPCsharpexample
{
#region Interfaces
[ComVisible(true), Guid(InteropUserControl.EventsId),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface __InteropUserControl
{
[DispId(1)]
void Click();
[DispId(2)]
void DblClick();
[DispId(3)]
void tbScroll();
[DispId(4)]
void tbValuechanged();
}
[Guid(InteropUserControl.InterfaceId), ComVisible(true)]
public interface _InteropUserControl
{
[DispId(1)]
bool Visible { [DispId(1)] get; [DispId(1)] set; }
[DispId(2)]
bool Enabled { [DispId(2)] get; [DispId(2)] set; }
[DispId(3)]
int ForegroundColor { [DispId(3)] get; [DispId(3)] set; }
[DispId(4)]
int BackgroundColor { [DispId(4)] get; [DispId(4)] set; }
[DispId(5)]
Image BackgroundImage { [DispId(5)] get; [DispId(5)] set; }
[DispId(6)]
void Refresh();
[DispId(7)]
void tbsetbackcolor(int testval);
[DispId(8)]
int tb1backgroundcolor { [DispId(8)] get; [DispId(8)] set; }
[DispId(9)]
int tb1value { [DispId(9)] get; [DispId(9)] set; }
}
#endregion
[Guid(InteropUserControl.ClassId), ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces("IOPCsharpexample.__InteropUserControl")]
[ComClass(InteropUserControl.ClassId, InteropUserControl.InterfaceId, InteropUserControl.EventsId)]
public partial class InteropUserControl : UserControl, _InteropUserControl
{
#region VB6 Interop Code
#if COM_INTEROP_ENABLED
#region "COM Registration"
public const string ClassId = "e7c6c97a-af38-4d57-9980-9edd60e1b45c";
public const string InterfaceId = "2aab63cc-a9df-4197-89f4-44150a746301";
public const string EventsId = "1eeca2b9-97ac-4c1c-8dc1-770c941c8ebd";
[EditorBrowsable(EditorBrowsableState.Never)]
[ComRegisterFunction]
private static void Register(System.Type t)
{
ComRegistration.RegisterControl(t);
}
[EditorBrowsable(EditorBrowsableState.Never)]
[ComUnregisterFunction]
private static void Unregister(System.Type t)
{
ComRegistration.UnregisterControl(t);
}
#endregion
#region "VB6 Events"
public delegate void ClickEventHandler();
public delegate void DblClickEventHandler();
public new event ClickEventHandler Click;
public event DblClickEventHandler DblClick;
private void InteropUserControl_Click(object sender, System.EventArgs e)
{
if (null != Click)
Click();
}
private void InteropUserControl_DblClick(object sender, System.EventArgs e)
{
if (null != DblClick)
DblClick();
}
#endregion
#region "VB6 Properties"
public new bool Visible
{
get { return base.Visible; }
set { base.Visible = value; }
}
public new bool Enabled
{
get { return base.Enabled; }
set { base.Enabled = value; }
}
public int ForegroundColor
{
get
{
return ActiveXControlHelpers.GetOleColorFromColor(base.ForeColor);
}
set
{
base.ForeColor = ActiveXControlHelpers.GetColorFromOleColor(value);
}
}
public int BackgroundColor
{
get
{
return ActiveXControlHelpers.GetOleColorFromColor(base.BackColor);
}
set
{
base.BackColor = ActiveXControlHelpers.GetColorFromOleColor(value);
}
}
public override System.Drawing.Image BackgroundImage
{
get{return null;}
set
{
if(null != value)
{
MessageBox.Show("Setting the background image of an
Interop UserControl is not supported, please use a PictureBox instead.",
"Information");
}
base.BackgroundImage = null;
}
}
#endregion
#region "VB6 Methods"
public override void Refresh()
{
base.Refresh();
}
private void InteropUserControl_LostFocus(object sender, System.EventArgs e)
{
ActiveXControlHelpers.HandleFocus(this);
}
public InteropUserControl()
{
InitializeComponent();
this.DoubleClick += new System.EventHandler(this.InteropUserControl_DblClick);
base.Click += new System.EventHandler(this.InteropUserControl_Click);
this.LostFocus += new System.EventHandler(InteropUserControl_LostFocus);
this.ControlAdded += new ControlEventHandler(InteropUserControl_ControlAdded);
this.TB1.ValueChanged += new System.EventHandler(TB1_ValueChanged);
this.TB1.Scroll += new System.EventHandler(TB1_Scroll);
this.OnCreateControl();
}
[SecurityPermission(SecurityAction.LinkDemand, Flags =SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref System.Windows.Forms.Message m)
{
const int WM_SETFOCUS = 0x7;
const int WM_PARENTNOTIFY = 0x210;
const int WM_DESTROY = 0x2;
const int WM_LBUTTONDOWN = 0x201;
const int WM_RBUTTONDOWN = 0x204;
if (m.Msg == WM_SETFOCUS)
{
this.OnEnter(System.EventArgs.Empty);
}
else if( m.Msg == WM_PARENTNOTIFY &&
(m.WParam.ToInt32() == WM_LBUTTONDOWN || m.WParam.ToInt32() == WM_RBUTTONDOWN))
{
if (!this.ContainsFocus)
{
this.OnEnter(System.EventArgs.Empty);
}
}
else if (m.Msg == WM_DESTROY && !this.IsDisposed && !this.Disposing)
{
this.Dispose();
}
base.WndProc(ref m);
}
private void InteropUserControl_ControlAdded(object sender, ControlEventArgs e)
{
ActiveXControlHelpers.WireUpHandlers(e.Control, ValidationHandler);
}
internal void ValidationHandler(object sender, System.EventArgs e)
{
if( this.ContainsFocus) return;
this.OnLeave(e);
if (this.CausesValidation)
{
CancelEventArgs validationArgs = new CancelEventArgs();
this.OnValidating(validationArgs);
if(validationArgs.Cancel && this.ActiveControl != null)
this.ActiveControl.Focus();
else
{
this.OnValidated(e);
}
}
}
#endregion
#endif
#endregion
public delegate void tbValueChangedEventHandler();
public event tbValueChangedEventHandler tbValuechanged;
private void TB1_ValueChanged(object sender, System.EventArgs e)
{
if (tbValuechanged != null)
{
tbValuechanged();
}
}
public event tbScrollEventHandler tbScroll;
public delegate void tbScrollEventHandler();
private void TB1_Scroll(object sender, System.EventArgs e)
{
if (tbScroll != null)
{
tbScroll();
}
}
public void tbsetbackcolor(int testval)
{
Color myColor = ColorTranslator.FromWin32(testval);
TB1.BackColor = myColor;
BackColor = myColor;
}
public int tb1backgroundcolor
{
get { return ActiveXControlHelpers.GetOleColorFromColor(TB1.BackColor); }
set { TB1.BackColor = ActiveXControlHelpers.GetColorFromOleColor(value); }
}
public int tb1value
{
get { return TB1.Value; }
set { TB1.Value = value; }
}
}
}
Note that the two event stubs created by double clicking on the event name in the IDE were moved to the end of the class, after the final ‘#endregion
’ label so most of the code outside the interface
section is together. In a nutshell, to expose the event to PB, you have to:
- Add the events after the
InitializeComponent
call on the class - Create a
public
eventhandler delegate - Create a
public
event which the delegate references - Code the event (double clicking on the control – event list gives you the event ‘skeleton’
- Add the event to the event interface
To code a property or method which can be called from PB, you have to:
- Create a
public
method with parameters and return values (if needed) OR a public
property with both a get
and set
section. - Add the method/property to the property interface
Important Note: Unlike the default for Visual Basic (or PowerBuilder for that matter), C# is strongly typed. This means that a method with the name ‘MyEvent
’ is not the same as ‘myevent
’, ‘Myevent
’ or ‘myEvent
’. Quite often, your code will compile fine, but when you try to use your component in PB, you will crash and burn.
When this is built in Visual Studio, a DLL is created and registered on the machine. If you want to take the DLL to a different machine, you will have to manually register the DLL in order to use it in PowerBuilder.
Using the Control in PowerBuilder
So now to add the control in Powerbuilder, you click on the Insert OLE control icon:
Choose the Insert Control tab and then the control from the control list.
The example app created to demonstrate the VB.NET component is expanded to include the C# component as well. A few more PB controls are added to the form which results in this:
The code in the clicked event of the Set Color button follows:
long ll_vb_color, ll_cs_color
// set the desired color on the controls
IF sle_1.text = string(RGB(0,174,221)) THEN
ll_vb_color = 15780518
ll_cs_color = RGB(168,255,168)
ELSE
ll_vb_color = RGB(0,174,221)
ll_cs_color = RGB(196,240,16)
END IF
// call method on VB .Net control
ole_3.object.event tbSetBackColor(ll_vb_color)
// call method on C# .Net control
ole_2.object.tbSetBackColor(ll_cs_color)
// get the color value using the
sle_1.text = string(ole_3.object.tb1backgroundcolor())
// get the color value using the
sle_2.Text = string(ole_2.object.tb1backgroundcolor())
The only difference in the method calls to the controls (tbSetBackColor
method) is in VB you qualify it as an event.
The code in the tbvaluechanged
event on the C# control is:
st_4.text = string(ole_2.object.tb1value())
Which is calling the C# get method of the tb1value
property.
In the tbscroll
event on the control is the following:
long ll_val, ll_color
//report on the current value of the control
ll_val = ole_2.object.tb1value()
//move the PB control
vtb_1.position = ll_val
As is the case for the VB control, you must have some code in the events you expose or you will get an unhandled exception error.
Running the Sample Application
Clicking the Set Color button changes the .NET controls:
Moving the VB control:
Moving the C# control:
The PowerBuilder code was written in version 12.5.1. Within the csharpinteroppb are export files if you are working with an earlier version.