How to use .Net visual controls in PowerBuilder Classic
Using non-visual .Net classes in PowerBuilder Classic is somewhat straightforward. Provided the assembly (and the classes and methods within it) have been marked as COM Visible, you can run REGASM on them to create OLE registry entries and then use the class through OLE Automation. I have a video on the SAP D&T Academy that demonstrates the process.
Using Net visual controls takes a bit more work. There is an add-in for Visual Studio Professional called the Microsoft InteropForms Toolkit that essentially puts an ActiveX wrapper around a .Net visual user object that can contain one or more visual controls. Some observations:
- The toolkit is an add-in for Visual Studio. As a result, it will only work in the Professional or higher versions of Visual Studio. The Express versions of VB6 Visual Studio don’t support add ins. Apparently some people have found a way around the limitation, but doing so is beyond the scope of this article.
- The toolkit only supports the VB.net language. If you are more comfortable with C#, there are third party utilities that extend the toolkit so that you can use C# instead. That will not be covered in this article, but you can see Interop Forms Toolkit for C&#35; – Home for one example.
- The toolkit exposes Windows Forms controls. However, you can add an ElementHost control to the toolkit control and then use that to host a WPF control. Once again, that is beyond the scope of this article.
I have a video on the SAP D&T Academy as well that covers this technique. This blog post will look at a different sample implementation than the one used in that video and will include the code used.
Note that the code used here was adapted from a CodeProject sample of a stand alone multi-page TIF viewer. The code was converted from C# to VB.Net and then adapted to function as a single user object with methods rather that a stand alone viewer.
So, the first thing we’re going to do is fire up Visual Studio.Net and create a new VB6 Interop UserControl.
Drag a PictureBox control over from the Toolbox onto the user object in the designer and then set the Dock property of the control to Fill. Since we’ve only got one control in this user object, this will ensure that the PictureBox will always fill the entire contents of the user control when it’s resized.
In the code editor, we’re now going to declare some instance variables in the user object that we will use to track a few properties we’re interested in the
“VB6 Interop Code” Region, just after the “#If COM_INTEROP_ENABLED Then” declaration.
Private _CurrPage As Integer = 0 'defining the current page (its some sort of a counter) Private _Opened As Boolean = False 'if an image was opened Private _NumPages As Integer 'the number of pages in the tiff file Private _FileName As String 'the name of the file that was opened
We want a couple of those to be read only properties as well for the control, so we add those into the “VB6 Properties” Region:
Public ReadOnly Property NumberOfPages() As Integer Get Return _NumPages + 1 End Get End Property Public ReadOnly Property CurrentPageNumber() As Integer Get Return _CurrPage + 1 End Get End Property
The reason that we’re adding 1 to each of these values before returning it is because the mechanism that tracks the page numbers internal to the control is zero index based (the first page is page 0). We need to adjust that before displaying it to the user who understands page numbers as one index based.
Finally, we’re going to add some methods into the “VB6 Methods” Region, one of which is internal and the others which we’ll be using from PowerBuilder to interact with the control.
The first method is the internal method that is used to refresh the image in the control when a TIF is first loaded or the user navigates to a different page:
Private Sub RefreshImage() Dim myBmp As Image 'a new occurrence of Image for viewing Dim myImg As Image 'setting the selected tiff myImg = System.Drawing.Image.FromFile(_FileName) 'setting the image from a file _NumPages = myImg.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page) - 1 'the first page is 0 so we must correct the number of pages to -1 myImg.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, _CurrPage) 'going to the selected page myBmp = New Bitmap(myImg, PictureBox1.Width, PictureBox1.Height) 'setting the new page as an image 'Description on Bitmap(SOURCE, X,Y) PictureBox1.Image = myBmp 'showing the page in the pictureBox1 End Sub
Recreating myImg an _NumPages each time the function is called is perhaps a bit of overkill, but I wasn’t interesting in trying to refactor the code, just convert it for this sample.
The following function is used to pass a filename from PowerBuilder into the control so that the control will read it and display the first page:
Public Sub OpenFile(ByVal FileName As String) _FileName = FileName _CurrPage = 0 'reseting the counter RefreshImage() 'refreshing and showing the new file _Opened = True 'a file was opened. End Sub
This function is used to navigate forward one page in the TIF file:
Public Sub NextPage() If (_Opened) Then 'the button works if the file is opened. you could go with button.enabled If (_CurrPage = _NumPages) Then 'if you have reached the last page it ends here 'the "-1" should be there for normalizing the number of pages _CurrPage = _NumPages Else _CurrPage = _CurrPage + 1 RefreshImage() End If End If End Sub
And finally, this function is used to navigate back one page in the TIF file:
Public Sub PriorPage() If (_opened) Then 'the button works if the file is opened. you could go with button.enabled If (_CurrPage = 0) Then 'it stops here if you reached the bottom, the first page of the tiff _CurrPage = 0 Else _CurrPage = _CurrPage - 1 'if its not the first page, then go to the previous page RefreshImage() 'refresh the image on the selected page End If End If End Sub
That’s a good start. The user object could obviously be embellished. For example, offering methods to navigate to a certain page directly, zoom in and out on the displayed image, print one or more pages of the image, etc. could all be added later.
Once you compile the project, Visual Studio.Net will create the registry entries that make the user object available as an ActiveX control. Open up PowerBuilder Classic, open a window control and start to insert an OLE Control onto the window. In the dialog that appears, select the third tab (Insert Control), and then scroll to the name of the control you created in Visual Studio.net. In my sample, I give it the rather unimaginative name of “TiffViewerControl.TiffViewerControl”
In the PowerBuilder window, I added buttons to open a TIF file and display it in the control, to move forward one page and to move backwards one page. I also have a static text field that display the current page number and number of pages in the document.
Note that the functions and properties of the user object do not display in the PowerBuilder IDE. You end up calling them as methods and properties of the ole control object attribute. PowerBuilder compiles that without question and only attempts to validate that the references are valid when the code is run.
The script in the button that opens a TIF file for display is as follows:
Integer li_rc String ls_pathname, ls_filename li_rc = GetFileOpenName ( "Select a TIFF file", ls_pathname, ls_filename, "TIF", "TIF Files (*.tif),*.tif" ) IF li_rc = 1 THEN ole_1.Object.OpenFile ( ls_pathname ) of_displaylocation() END IF
To move forward one page:
To move backwards one page:
The of_displaylocation function called by all three scripts is the one that populates the static text field with the current and total page numbers:
integer li_currpage integer li_numpages li_currpage = ole_1.Object.CurrentPageNumber li_numpages = ole_1.Object.NumberOfPages st_location.text = "Page " + String ( li_currpage ) + " of " + String ( li_numpages )
Finally, I have some code in the resize event of the window so that the user object resizes when the window is resized.
ole_1.Resize ( newwidth - 150, newheight - 250 )
With that, we’re done. Run the app, load up a TIF file and start browsing through it. For this sample, I converted the 142 page PowerBuilder ORCA manual from PDF to TIF.
The sample code, both for Visual Studio.Net and for PowerBuilder 12.5 Classic, is available on my Google Drive.
Good afternoon from peru, please can you upload the sample code again?. the link from google drive is gone.
I updated the link. I moved it into my PowerBuilder Samples folder with the other samples. Look for TiffViewerDemo.zip.
I have tried this with success on my PC, but when I try to run the same Powerbuilder app on another PC I am getting GPFs. The Powerbuilder app just crashes.
I have registered the Interop DLL as a .Net Assembly, but it still doesn't work. I can't register it using regsvr32.
Is there another way to register these DLLs or have I missed something?
Thanks in advance
What do you mean by "registered the Interop DLL as a .Net assembly"? Did you add it to the GAC? Did you run REGASM on it?
We tried to do something like this 2 years ago, but we were plagued with crashing. See this thread PowerBuilder 12.5.2 app crashing often (the last post in particular). I'm interested to hear your thoughts on this issue we've had, or if you've encountered anything similar.
I'm afraid of ever trying .NET controls and Interop again (once bitten, twice shy) but it would be sooooo useful if it was stable.
There's not much to go on in that thread. You indicated it was a custom COM component, but didn't mention if it was based on the Forms Interop Toolkit or something else. You're also getting a .Net exception, which means it might be an issue in the code within the component.
In any event, I haven't experienced anything like that.
Who created the component? Are they aware of the issue? Have they given any feedback?
I created the COM component using the Forms Interop Toolkit.
I wrote a powerbuilder script that would open a window with the custom COM (using the third party .NET control) on it, load a image (like your example), and close the window. This is where I was able to really see the exceptions/crashing pop up.
I also tried this without the third party control and just a regular .NET control in the custom COM (very basic) and got similar results. Which led me to believe it was not the third party control that was causing the issue.
Thanks for the reply, do you have success using this technique production (stability wise)?