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 lets 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:
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.
Lets fix these problems quickly to be able to 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, that we have also seen in the VOPAD example by either adding an Extension Method or by subclassing the Toolbar class.
•Just like in the VOPAD example I prefer the extension method.
•Change the class name and method declaration. We will create 2 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 that calls 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..
So locate the 2 lines with SELF:ToolBar:ShowButtonMenu and change that to ((Toolbar) SELF:ToolBar):ShowButtonMenu. You 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.
Maybe you would be tempted to add the Extension methods to the USUAL type, so you would not have to add the casts to the code that calls ShowButtonMenu.
That will compile, but unfortunately will 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. That 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. 2 of these mention the type cOleMethod. Goto 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 app 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. I twill 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. So comment out the body of the Display method (do not throw the code away 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.
The VO compatible GUI classes inside Vulcan do not support ActiveX Controls. Windows Forms however 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 2 possibilities here:
1.Replace the whole 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 by far the easiest to understand, but we will have to create a whole new window and we will have to change the calling code as well.
We leave it up to you to make the choice for your own apps.
In this example we will the choose the second approach.
For this method we use a Windows.Forms.Form window as "Host" for the ActiveX control.
We will instantiate that window and will 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:
•Tick the checkbox in front of the control and press Ok. This should add the ActiveX to the toolbox:
•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.
•Goto 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 goto 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 namespace is the namespace of the generated ActiveX and the third namespace if that of the other types that we need, such as enums and events.
•Add the following 2 fields (no need to elaborate I think):
EXPORT oHost as webBrowserHost
EXPORT oWebBrowser as AxWebBrowser
•Goto 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
•And add the following methods to make sure that the ActiveX has the same with 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
•And we need to "restore" the old behavior to display the HTML in the browser window, that we commented out before.
So goto 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" )
And 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. Also 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