Show/Hide Toolbars

XSharp

Navigation: X# Documentation > Migrating apps from VO to X#

Example 5: OCX - The Email Client Example

Scroll Prev Top Next More

This example shows how to migrate an application that uses an ActiveX/OCX Control.
We are using the Email example from Visual Objects that you can find in the Examples folder, subfolder Email.

The problem that we can expect here is that the X# Runtime (and also Vulcan rumtime) does not support ActiveX controls.

So let's try to solve this.

 

First, run VOXporter and create a Visual Studio solution from the AEF.

Compile and run in Visual Studio.

We will get 2 messages:

Email1

The first message shows the biggest problem in this example. The second message was inserted by the Xporter to warn us that the original code was adding a method to a class that exists inside the VO Gui classes.

 

Let's fix these problems quickly so that we can compile the app. We will add the OCX later:

Click on the warning. You will see that the XPorter has added a CLASS ToolBar_external_class that inherits from Toolbar. The original code was trying to add the ShowButtonmenu method to the existing Toolbar class.

We can solve this problem, which we have also seen in the VOPAD example, by either adding an Extension Method, or by sub-classing the Toolbar class.

Just like in the VOPAD example, I prefer the extension method.

Change the class name and method declaration. We will create two overloads, because the symTb parameter is optional:

STATIC CLASS ToolBarExtensions
  STATIC METHOD ShowButtonMenu(SELF tbSelf as Toolbar, nButtonID as LONG, oButtonMenu as Menu) AS VOID
     tbSelf:ShowButtonMenu(nButtonID, oButtonMenu, #MAINTOOLBAR)
    RETURN
STATIC METHOD ShowButtonMenu(SELF tbSelf as Toolbar, nButtonID as LONG, oButtonMenu as Menu,symTb as Symbol) AS VOID
 

Remove the Default() line and replace SELF in the body of the original ShowButtonMenu with tbSelf.

We will also have to make some changes to the code calling this method. This is due to the fact that the code calls ShowButtonMenu on the Toolbar access from the window class. This Toolbar access is untyped and therefore returns a USUAL..
To do this, locate the two lines with SELF:ToolBar:ShowButtonMenu and change that to ((Toolbar) SELF:ToolBar):ShowButtonMenuYou cannot use the oToolbar field of the Window class, because the DataWindow class will return the Toolbar from its framewindow in stead of its own toolbar.
In the improved VO SDK that we will include with our X# runtime, we will solve this problem by strongly typing properties such as Window:Toolbar.
You may be tempted to add the Extension methods to the USUAL type, to avoid having to add the casts to the code that calls ShowButtonMenu.
Although that will compile, it will unfortunately produce a problem at runtime. The X# compiler (like the Vulcan and VO Compilers) knows that the USUAL type is special and will not try to emit a method call, but will produce code that calls Send() on the usual to call its method. And the Vulcan Runtime does not handle extension methods inside the Send() function.

We can confirm that this works later when we press the "Reply" button on the toolbar. That should bring up the menu with "Reply to Sender" and "Reply to All".

 

Now, it is time to fix the ActiveX/OCX problem

Click on the error about OleControl.

As a quick workaround, we will change the code and let webbrowser inherit from MultiLineEdit. This gives us a control that will certainly work. We will implement the OCX later. To do so, go to the Class Webbrowser.PRG and change the INHERIT clause. It says INHERIT OleControl now. Change that to INHERIT MultiLineEdit.

Compile again and now some other errors will be shown. Two of these mention the type cOleMethod. Go to this code by double clicking the error.

You will see the Quit method of the Webbrowser class. This code uses an internal class and internal methods from the VO OLE Classes. Comment out the contents of this method for now.

Compile again, and you will see that only a few errors are left. Some of these are the same as the error in the VOPAD Example and require that we change the Font property to ControlFont. Correct that.

One error points to an incorrect '+' operator: in the line

cTemp +=  +"; "+ cEntry

This is an obvious error in the original VO code that was never picked up by the VO Compiler. Remove the + before the double quote.

The last error comes from the constructor of the Webbrowser class. It is calling the CreateEmbedding method from the OleControl. This method does not exist in the MultiLineEdit class, so we comment it out for now. We will deal with the Webbrowser later.

The rest of the code should compile without problems, after commenting out the call to SELF:CreateEmbedding().

You should be able to run the application now.

There will be a runtime error if you try to open the Address Book, because it uses the Databrowser control which depends on Cato3Cnt.dll. Fix this by copying the cato3*.dll and msvcrt.dll from the Cavo28\Redist folder to your output folder.

Recompile and run the example. It will now produce an error inside the Display method of the Webbrowser class (DisplayHtml if you have used the Email example from VO 2.8 SP4).
This method takes the content of the email, writes it to disk and calls the Navigate method of the Webbrowser control (late bound, using the Send() function of VO). This will not work.
Since we have changed the webbrowser control and made it a multi-line edit, we can change this behavior. Instead of writing the email text to disk, we can simply assign it to the TextValue property of the MultiLineEdit. In order to do this, comment out the body of the Display method (do not throw the code away as we will need it later), and replace it with:

SELF:TextValue := cText

After that, the sample should run without problems. You can also display the emails. Of course it will not show HTML properly but that is the next step.

How to add the ActiveX to the code

The VO compatible GUI classes inside Vulcan do not support ActiveX Controls. However, Windows Forms has great support for ActiveX Controls.

We will use the ActiveX support from Windows Forms to add the ActiveX control to the example.

There are two possibilities here:

1.Replace the entire Email Display window with a Windows Forms window;

2.Use a trick to use Windows Forms to show the ActiveX control and merge that control into our VO GUI app.

The first solution is much easier to understand, but requires creating a whole new window and changing the calling code as well.

It is up to you to make the choice for your own apps.

In this example, we will the choose the second approach.

Create a Windows Forms Window to display the email

For this method, we use a Windows.Forms.Form window as "Host" for the ActiveX control.
We will instantiate that window, grab the windows handle to the control, and link that windows handle with our VO GUI window.

To do this, you must take the following steps:

Right Click on the project icon in the solution explorer and select "Add New Item"

This brings up a list of possible new items. Choose the icon for Windows Forms Form, give it a meaningful name (such as "EmailDisplayForm.prg"), and click Add.

This will open the Form Designer window.

Open the ToolBox. The webbrowser control will not be in there.

Right click on an empty area in the toolbox and select "Choose Items...". This will bring up a dialog where you can control the contents of the ToolBox.

Select the "COM Components" Tab and scroll down until you see the Microsoft Web Browser control:
 
Email3

Tick the checkbox in front of the control and press Ok. This should add the ActiveX to the toolbox:
 

Email4

You can drag the control to a different place in the Toolbox if you are not happy with where it landed.

Now, drag the Control from the ToolBox to the form. There is no need to size or move the control.

Visual Studio will add two references to our project. These are:

oAxSHDocVw, a type library that contains that code for the actual ActiveX control;

oSHDocVw, a type library that contains code for the supporting automation server interfaces and classes.

The form editor will add a field named axWebBrowser1 to the form. This field is of the type AxSHDocVw.AxWebBrowser.

Go to the property window and change the Modifiers Field to change it from Private to it Public (Export).
That will make the field accessible outside of the webBrowserHost class.

Save the code, and close the form.

Now go to the Webbrowser class.

Add the following using clauses to the top of the file:

  using Email
  using AxShDocVw
  using ShDocVw

 

The first using is the namespace where the webBrowserHost window is generated, the second is the namespace of the generated ActiveX and the third is that of the other types that we need, such as enums and events.

Add the following two fields (no need to elaborate I think):

  EXPORT oHost as webBrowserHost
  EXPORT oWebBrowser as AxWebBrowser
 

Go to the constructor of the Webbrowser class and add the following lines of code (in stead of the CreateEmbedding() that we commented out before)

SELF:oHost := webBrowserHost{}           // Create the host window, do not show !
SELF:oWebBrowser := SELF:oHost:axWebBrowser1   // Get the ActiveX on the form
SetParent(oWebBrowser:Handle, self:Handle())   // Using Windows API "steal" its handle and link to the MLE
SELF:oWebBrowser:Visible := TRUE           // make the webbrowser visible
SELF:Okay := TRUE

Then, add the following methods to make sure that the ActiveX has the same width and height as the MultiLineEdit that is its owner, and to make sure it is properly destroyed:

METHOD Resize(oEvent)
LOCAL oDim as Dimension
SUPER:Resize(oEvent)
oDim := SELF:Size
IF oDim:Width > 0
  SELF:oWebBrowser:SuspendLayout()
  SELF:oWebBrowser:Location := System.Drawing.Point{0,0}
  SELF:oWebBrowser:Size := System.Drawing.Size{oDim:Width,oDim:Height}
  SELF:oWebBrowser:ResumeLayout()
ENDIF
RETURN NIL
 
METHOD Destroy()
SUPER:Destroy()
SELF:oWebBrowser:Dispose()
SELF:oHost:Dispose()
RETURN NIL

We also need to "restore" the old behavior to display the HTML in the browser window, that we commented out before.
To do this, go to the WebBrowser:Display() method (DisplayHtml for VO 2.8 SP4) and restore the old code and change

Send(SELF, #Navigate, cFileName)

into

SELF:oWebBrowser:Navigate(cFileName)

so you change this into an early bound method call.

To finish our work, browse through the source of the webbrowser class and find lines that call Navigate, such as

Send( SELF, #Navigate, "#top" )

and change these to early bound method calls:

SELF:oWebBrowser:Navigate("#top" )

Also, look for lines like:

Send( SELF, #ExecWB, OLECMDID_PRINT, OLECMDEXECOPT_DODEFAULT, NIL, NIL )

and change these to early bound method calls using enums in the type library. Additionally, remove the NIL values:

SELF:oWebBrowser:ExecWB(OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT )

That wraps it up. Everything works now, including the PrintPreview and Print functionality.

Of course, you can now also use the activeX events and respond to them.
You have to do that the .Net way. Something like this:

SELF:oWebBrowser:NavigateComplete2 += NavigateComplete2

 

and then the implementation

METHOD NavigateComplete2(sender AS OBJECT, e AS DWebBrowserEvents2_NavigateComplete2Event) AS VOID
      SELF:Owner:StatusBar:SetText("Showing file:" +e:uRL:ToString())

You will find the "code before" and "code after" in the XSharp Examples folder.