Powerbuilder OLE – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 17:24:52 +0000 en-US hourly 1 PowerBuilder – Using C# Visual Objects in PB Classic Applications https://anvil-of-time.com/powerbuilder/powerbuilder-using-c-visual-objects-in-pb-classic-applications/ Mon, 18 Jun 2012 22:16:01 +0000 http://anvil-of-time.com/wordpress/?p=1373 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();
        //add additional events visible in VB6
        // these events exposed and shown in PB event list
        [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();
        //add additional properties visible in VB6
        // PB can call these
        [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"

        //These  GUIDs provide the COM identity for this class 
        //and its COM interfaces. If you change them, existing 
        //clients will no longer be able to access the class.

        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";

        //These routines perform the additional COM registration needed by ActiveX controls
        [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"

        //This section shows some examples of exposing a UserControl's events to VB6.  Typically, you just
        //1) Declare the event as you want it to be shown in VB6
        //2) Raise the event in the appropriate UserControl event.
        public delegate void ClickEventHandler();
        public delegate void DblClickEventHandler();
        public new event ClickEventHandler Click; //Event must be marked as new since .NET UserControls have the same name.
        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"

        //The following are examples of how to expose typical form properties to VB6.  
        //You can also use these as examples on how to add additional properties.

        //Must Shadow this property as it exists in Windows.Forms and is not overridable
        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();
            }

            //Ensures that tabbing across VB6 and .NET controls works as expected
            private void InteropUserControl_LostFocus(object sender, System.EventArgs e)
            {
                ActiveXControlHelpers.HandleFocus(this);
            }

            public InteropUserControl()
            {
                //This call is required by the Windows Form Designer.
                InitializeComponent();

                //' Add any initialization after the InitializeComponent() call.
                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);
                //////// new events to expose to PB
                this.TB1.ValueChanged += new System.EventHandler(TB1_ValueChanged);
                this.TB1.Scroll += new System.EventHandler(TB1_Scroll);
                //////////
                //'Raise custom Load event
                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)
                {
                    //Raise Enter event
                    this.OnEnter(System.EventArgs.Empty);
                }
                else if( m.Msg == WM_PARENTNOTIFY && (m.WParam.ToInt32() == WM_LBUTTONDOWN || m.WParam.ToInt32() == WM_RBUTTONDOWN))
                {

                    if (!this.ContainsFocus)
                    {
                        //Raise Enter event
                        this.OnEnter(System.EventArgs.Empty);
                    }
                }
                else if (m.Msg == WM_DESTROY && !this.IsDisposed && !this.Disposing)
                {
                    //Used to ensure that VB6 will cleanup control properly
                    this.Dispose();
                }

                base.WndProc(ref m);
            }

            //This event will hook up the necessary handlers
            private void InteropUserControl_ControlAdded(object sender, ControlEventArgs e)
            {
                ActiveXControlHelpers.WireUpHandlers(e.Control, ValidationHandler);
            }

            //Ensures that the Validating and Validated events fire appropriately
            internal void ValidationHandler(object sender, System.EventArgs e)
            {
                if( this.ContainsFocus) return;

                //Raise Leave event
                this.OnLeave(e);

                if (this.CausesValidation)
                {
                    CancelEventArgs validationArgs = new CancelEventArgs();
                    this.OnValidating(validationArgs);

                    if(validationArgs.Cancel && this.ActiveControl != null)
                        this.ActiveControl.Focus();
                    else
                    {
                        //Raise Validated event
                        this.OnValidated(e);
                    }
                }
            }
        #endregion

#endif
        #endregion

        //Please enter any new code here, below the Interop code
        /////////// events to expose to PB
        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();
            }
        }
        ////////////
        //method to call from PB
        public void tbsetbackcolor(int testval)
        {
            Color myColor = ColorTranslator.FromWin32(testval); //translate numeric color
            TB1.BackColor = myColor; //set background of trackbar
            BackColor = myColor; // set background of user control
        }
        ///////////
        // Property get/set to call from PB
        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 doubleclicking on the event name in the IDE were moved to the end of the class, after the final ‘#endregion’ lable so most of the code outside the interface section is together. In a nutshell to expose the event to PB you have to:
A) add the events after the InitializeComponent call on the class
B) create a public eventhandler delegate
C) create a public event which the delegate references
D) code the event (double clicking on the control – event list gives you the event ‘skeleton’
E) add the event to the event interface

To code a property or method which can be called from PB you have to:
A) create a public method with parameters and return values (if needed) OR a public property with both a get and set section.
B) 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 file, 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 'getter' method in VB .Net control
sle_1.text = string(ole_3.object.tb1backgroundcolor())
// get the color value using the 'getter' method in C# .Net control
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.

]]>
PowerBuilder – Timing Out a Windows Session https://anvil-of-time.com/powerbuilder/powerbuilder-timing-out-a-windows-session/ Tue, 12 Jun 2012 23:25:38 +0000 http://anvil-of-time.com/wordpress/?p=1368 There are a variety of techniques to locking or timing out an application after a certain amount of inactivity. With earlier versions of Windows (XP and prior) a common approach was to invoke the screen saver via a Send command.

send(handle(This),274,61760,0)

This doesn’t work with Windows 7 (or Vista). Try the following:

integer li_rc
OleObject lole_wsh

lole_wsh = CREATE OleObject
li_rc = lole_wsh.ConnectToNewObject ( "WScript.Shell" )
IF li_rc > 0 THEN
   lole_wsh.Run ("rundll32.exe user32.dll,LockWorkStation")
   lole_wsh.DisconnectObject()
END IF

This can be used in Windows XP as well.

]]>
PowerBuilder – Using .Net Visual Controls in PB Classic Applications https://anvil-of-time.com/powerbuilder/powerbuilder-using-net-visual-controls-in-pb-classic-applications/ Tue, 05 Jun 2012 22:27:24 +0000 http://anvil-of-time.com/wordpress/?p=1335 This article describes the techniques and code used during my presentations at the Carolina Code Camp 2012 and at the May 2012 meeting of the North Carolina PowerBuilder User Group.

The techniques described here utilize Visual Basic .Net (coded in Visual Studio 2010) with the Interop Forms Toolkit available from Microsoft. The Interop Forms Toolkit was originally intended as a ‘bridge’ between the VB 6 world and VB .Net. It allows you to host any .Net control in any Win32 application that supports COM. The current version supports Visual Studio 2005, 2008, and 2010.

In this example we are creating a horizontal track bar control to use in a PB application (PB12.5). Why not use the PB control you say? Well, we want one which allows us to control the background color.

First create a project in Visual Studio and under ‘Visual Basic’ – ‘Windows’ choose the VB6 Interop UserControl. Give the control a meaningful name at the bottom, I picked ‘vb_trackbar’.

In the Solution Explorer you should see a number of components which are automatically created. Almost all the work is done in the ‘InterobUserControl.vb’ element

Double click on the ‘InteropUserControl.vb’ element to bring it up in design mode so the actual control can be built.

Choose a Trackbar from the tools window in Visual Studio

and drop it on the usercontrol. Rearrange its size and position as appropriate

Now choose a Label and drop it on the control.

Change the backcolor of the trackbar to yellow; the name to TB; the Margin to 0,0,0,0; the Maximum to 100; the TickFrequency to 10; and the TickStyle to Both.

Modify the background color of the label and the usercontrol itself to yellow. Rename the control to ‘IOP_Trackbar’, the label to ‘LB_Value’, and give the label a border.

Right click on the ‘InteropUserControl.vb’ element from the Project window and choose ‘View Code’

You will see code like this (this is from Visual Studio 2010).

Here is the code to add.

   'Please enter any new code here, below the Interop code

    ' event to show in PB
    Public Event tbScroll()
    Public Event tbValuechanged()

    Private Sub TB_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TB.Scroll
        LB_Value.Text = TB.Value.ToString()
        RaiseEvent tbScroll()
    End Sub
    Private Sub TB_Valuechanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TB.ValueChanged
        RaiseEvent tbValuechanged()
    End Sub

    ' method to call from PB
    Public Sub tbSetBackColor(ByVal testval As Integer)
        Dim myColor As Color = ColorTranslator.FromWin32(testval)

        TB.BackColor = myColor ' set color on trackbar
        LB_Value.BackColor = myColor ' set color on label
        BackColor = myColor ' set color on interop control
    End Sub
    ' Property get/set
    Public Shadows Property TBBackgroundColor() As Integer
        Get
            Return ActiveXControlHelpers.GetOleColorFromColor(TB.BackColor)
        End Get
        Set(ByVal value As Integer)
            TB.BackColor = ActiveXControlHelpers.GetColorFromOleColor(value)
        End Set
    End Property
    ' this is the value of the trackbar
    Public Shadows Property TBValue() As Integer
        Get
            Return TB.Value
        End Get
        Set(ByVal value As Integer)
            TB.Value = value
        End Set
    End Property

In the first portion there are two events we want to expose to PowerBuilder so we can write code in our PB app to respond to the control. These are ‘tbScroll’ and ‘tbValuechanged’. One is triggered in response to the Scroll event on the trackbar (which we named ‘TB’) and the other by the Valuechanged event. In the tbScroll event we are setting the Text in the label (we called it ‘LB_Value’) on the userobject to the Value property of the Trackbar.

We also are creating a method, tbSetBackColor, which can be called from our PB app to set the background colors of the userobject, trackbar, and label.

Finally we are exposing two properties of the Trackbar so we can either set or get them from our PB app. These are called ‘TBBackgroundColor’ and ‘TBValue’.

The last thing we need to do in Visual Studio is to add an Interface to the project so the events we are exposing can be seen from within PowerBuilder. From the Project menu choose ‘Add New Item’.

Then under the ‘Common Items’ choose ‘Interface’

In the code for the interface, replace it with the following.

<guid("ca16a9e8-02c7-43d2-90f6-4ef30885a338"), _="" interfacetype(cominterfacetype.interfaceisidispatch)=""> _
Public Interface iIOP_Trackbar
    <dispid(1)> _
    Sub tbScroll()
    <dispid(2)> _
    Sub tbValuechanged()
End Interface</dispid(2)></dispid(1)></guid("ca16a9e8-02c7-43d2-90f6-4ef30885a338"),>

The first thing to do now is to replace the example GUID with one of your own (Visual Studio can generate one). Also give the Interface a more meaningful name – ‘iIOP_Trackbar’ in this example. Within the interface definition are the two events which will be visible from within PowerBuilder.

With this done you can now build the solution from the ‘Build’ menu

This will create and register the dll on the machine.

Now in PowerBuilder the control will be available when you choose to add an userobject.

The events are visible in Powerscript.

Now in the tbscroll event on our ole control we put the following.

long ll_val
//report on the current value of the control
//this uses the 'getter' method to return the track bar value from the .net control
ll_val = ole_1.object.event tbvalue()
//move the PB control
htb_1.position = ll_val
// show value
st_1.text = string(ll_val)

Code in the tbvaluechanged event on the ole control

sle_2.text = 'tbvaluechanged at ' + string(Now(),'hh:mm:ss')

Code to set the color of the ole control.

long ll_color

// set the desired color on the control
IF sle_1.text = string(RGB(0,174,221)) THEN
	ll_color = 15780518
ELSE
	ll_color = RGB(0,174,221)
END IF
// call method on .Net control to set the background colors
ole_1.object.event tbsetbackcolor(ll_color)
// get the color value using the 'getter' method in .Net class
sle_1.text = string(ole_1.object.tbbackgroundcolor())

Running the application gives the following

See the original control background color. Clicking the Set Color button gives this

Moving the trackbar ole control results in this.

There are a couple of things to look out for when using the techniques described here. The first is that any events exposed via the .Net Interface must have some sort of Powerscript in them within PB. Failure to do this results in an ‘unhandled exception’ application crash. There may be some way to prevent this from within Visual Basic but I am not sure.

The other issue is that if you have to change your VB.Net component you must re add it to your window/form within PowerBuilder. Usually I will add a second ole object to the existing window, transfer all the code from the old ole control, delete the original control, and finally rename the new control to that of the old control. This is the biggest issue I have with working with ‘roll your own’ ole controls. If anyone knows of a work around for this I would like to hear it.

Visual studio and PB files can be downloaded from here.

The video presentation of this is on YouTube.

]]>
PowerBuilder – Reading Outlook Items https://anvil-of-time.com/powerbuilder/powerbuilder-reading-outlook-items/ Fri, 02 Sep 2011 01:20:40 +0000 http://anvil-of-time.com/wordpress/?p=855 Here is some code which reads data from Outlook (2007 was tested) into PowerBuilder. You basically need to create a window with a multiline edit and a button on it. Put this into the clicked event of the button.

To run it, open Outlook, select something (email message, task, etc.) then click the button on your window. There are many, many more methods and properties you have access to from PB via OLE to Outlook. The MSDN reference online is a big help

integer li_rc
long ll_itemcount, ll_i
oleobject lole_item, lole_outlook, lole_exp, lole_selecteditems
string ls_subject, ls_from, ls_to, ls_body, ls_msg
lole_outlook = CREATE oleobject
lole_exp = CREATE oleobject
lole_selecteditems = CREATE oleobject
li_rc = lole_outlook.ConnectToNewObject("outlook.application")
lole_exp = lole_outlook.ActiveExplorer()
// Outlook has to be running
If IsNull(lole_exp) THEN
	Messagebox('Outlook Error','Is Outlook currently running?')
	GOTO cleanup
END IF
li_rc = lole_exp.class
// caption is window name like "Inbox - Microsoft Outlook" or "Calendar - Microsoft Outlook"
ls_subject = lole_exp.caption

lole_selecteditems = lole_exp.selection
ll_itemcount = lole_selecteditems.count

FOR ll_i = 1 to ll_itemcount
	lole_item = CREATE oleobject
	lole_item = lole_exp.selection.item(ll_i)
	li_rc = lole_item.class
	CHOOSE CASE li_rc
		CASE 26 //appointment
			ls_body = lole_item.body
			ls_msg += '~r~n Appointment No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_body
			// lots of other stuff could be here
		CASE 40 // contact
			ls_body = lole_item.body
			ls_msg += '~r~n Contact No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_body
			
		CASE 43 // mail
			ls_subject = lole_item.subject
			ls_from = lole_item.sendername
			ls_to = lole_item.to
			ls_body = lole_item.body
			ls_msg = '~r~nSubject: ' + ls_subject + '~r~nFrom: ' + ls_from + '~r~nTo: ' + ls_to + '~r~nBody: ' + ls_body
			ls_msg += '~r~n Email No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_msg
			
		CASE 48 // task
			ls_subject = lole_item.subject
			ls_to = lole_item.owner
			ls_body = lole_item.body
			ls_msg = '~r~nSubject: ' + ls_subject + '~r~nOwner: ' + ls_to + '~r~nBody: ' + ls_body
			ls_msg += '~r~n Task No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_msg
			
	END CHOOSE
	DESTROY lole_item
NEXT
IF Len(ls_msg) > 0 THEN
	mle_1.text = ls_msg
ELSE
	mle_1.text = 'No items processed.'
END IF
cleanup:
DESTROY lole_selecteditems
DESTROY lole_exp
DESTROY lole_outlook
]]>
PowerBuilder – Add /Remove a Font at Runtime https://anvil-of-time.com/powerbuilder/powerbuilder-add-remove-a-font-at-runtime/ Thu, 13 Jan 2011 01:20:22 +0000 http://anvil-of-time.com/wordpress/?p=673 Here is a way to add (and remove) a font to the user’s machine at runtime. You can also use this to check if a user has a particular font installed since the code also shows how to list all the installed fonts (need MS Word for this). Way back in my PB5 days I coded a process to print checks. Rather than fool with preprinted forms this application printed all of the data to produce a check which could be processed by banks just like any other. The printer would have to use special magnetic ink and we also used a special MICR font for the bank routing and account numbers. Although the application never really made it into the ‘real’ world, I always thought the potential for fraud was significant. If the font were dynamically added and removed from the workstation, this potential would have been somewhat less.

The basic process makes use of two windows API calls: AddFontResource and RemoveFontResource. The declarations are as follows (both ANSI and Unicode):

Function int AddFontResourceA(string lpFileName) library "gdi32.dll"
Function int AddFontResourceW(string lpFileName) library "gdi32.dll"
Function int RemoveFontResourceA(string lpFileName) library "gdi32.dll"
Function int RemoveFontResourceW(string lpFileName) library "gdi32.dll"

To demonstrate the techniques, I’ve created a sample application.

In the ‘Load Fonts’ button clicked event there is the following:

integer li_fonts, li_i
long	ll_row
OLEObject lole_word, lole_fonts
string ls_file_name
dw_1.reset() // clear existing entries

lole_word = CREATE oleobject
lole_fonts = CREATE oleobject
TRY
	lole_word.connecttonewobject('word.application')
CATCH (runtimeerror a)
	Messagebox('Error','Error connecting with MS Word. Process terminating.')
	RETURN -1
END TRY
lole_word.visible = FALSE // dont want to see word
lole_fonts = lole_word.fontnames
li_fonts = lole_fonts.count
// build font list in datawindow
FOR li_i = 1 to li_fonts
	ll_row = dw_1.insertrow(0)
	dw_1.setitem(ll_row,'fontname',lole_fonts.item(li_i))
NEXT
dw_1.sort()
lole_word.quit() // close word
DESTROY lole_fonts
DESTROY lole_word

This uses OLE with MS Word to retrieve the list of font names installed on the system.

There is a font file included with the example which can be used as the temporary font in the demonstration. Run the application and click on the ‘Select Font File’ button to choose this file.

Now click the ‘Add Font’ button. The font’s name and a sample of it should appear in the appropriate fields.

If you click the ‘Load Fonts’ button again you will see the new font name in the list.

Finally, click the ‘Remove Font’ button to remove it. The ‘New Font Example’ text will now revert.

If you click the ‘Load Fonts’ button again you will see the new font name has been removed from the list.

The zip archive containing the PB files, exports, and the sample font file can be downloaded here.

]]>
PowerBuilder – Locating Windows folders https://anvil-of-time.com/powerbuilder/powerbuilder-locating-windows-folders/ Thu, 30 Dec 2010 01:20:44 +0000 http://anvil-of-time.com/wordpress/?p=644 Here is an easy way to find the paths to the various Windows folders.

oleobject lole_wshell

lole_wshell = create oleobject
lole_wshell.connecttonewobject( "wscript.shell")
is_mydocspath = lole_wshell.SpecialFolders("MyDocuments")
IF IsValid(lole_wshell) THEN Destroy lole_wshell

/* other folders:
AllUsersDesktop
AllUsersStartMenu
AllUsersPrograms
AllUsersStartup
Desktop
Favorites
Fonts
MyDocuments
NetHood
PrintHood
Programs
Recent
SendTo
StartMenu
Startup
Templates
*/
]]>
PowerBuilder – Getting Active Directory information https://anvil-of-time.com/powerbuilder/powerbuilder-getting-active-directory-information/ Tue, 28 Sep 2010 01:00:33 +0000 http://anvil-of-time.com/wordpress/?p=394 Here is a handy bit of OLE to retrieve Active Directory information.

oleobject ads
string ls_stuff
ads = CREATE OleObject

ads.ConnectToNewObject( "ADSystemInfo" )

ls_stuff = 'User: ' + String(ads.UserName)
ls_stuff += '~n~r' + 'Computer: ' + string(ads.ComputerName)
ls_stuff += '~n~r' + 'Domain: ' + string(ads.DomainDNSName)
ls_stuff += '~n~r' + 'Domain short: ' + string(ads.DomainShortName)
ls_stuff += '~n~r' + 'Forest: ' + string(ads.ForestDNSName)
ls_stuff += '~n~r' + 'Native Mode: ' + string(ads.IsNativeMode)
ls_stuff += '~n~r' + 'PDCRoleOwner: ' + string(ads.PDCRoleOwner)
ls_stuff += '~n~r' + 'SchemaRoleOwner: ' + string(ads.SchemaRoleOwner)
ls_stuff += '~n~r' + 'Site: ' + string(ads.SiteName)


MessageBox("Active Directory - Information",ls_stuff)
DESTROY(ads)

Sample output from the above example:

More information here:

From the Microsoft link

ADSystemInfo interface defines the following properties. All are read only

ComputerName
Retrieves the distinguished name of the local computer.

DomainDNSName
Retrieves the DNS name of the local computer domain, for example “example.fabrikam.com”.

DomainShortName
Retrieves the short name of the local computer domain, for example “myDom”.

ForestDNSName
Retrieves the DNS name of the local computer forest.

IsNativeMode
Determines whether the local computer domain is in native or mixed mode.

PDCRoleOwner
Retrieves the distinguished name of the NTDS-DSA object for the DC that owns the primary domain controller role in the local computer domain.

SchemaRoleOwner
Retrieves the distinguished name of the NTDS-DSA object for the DC that owns the schema role in the local computer forest.

SiteName
Retrieves the site name of the local computer.

UserName
Retrieves the Active Directory distinguished name of the current user, which is the logged-on user or the user impersonated by the calling thread.

]]>
PowerBuilder – OLE with Facsys to Fax a Document https://anvil-of-time.com/powerbuilder/powerbuilder-ole-with-facsys-to-fax-a-document/ Sat, 03 Oct 2009 01:23:01 +0000 http://anvil-of-time.com/2009/10/02/powerbuilder-ole-with-facsys-to-fax-a-document/ Here is a old post I put on Tek-Tips.com way back in 2001.

I have just implemented an OLE faxing solution in my PB7 app using Facsys.

This code creates the session, addresses the fax message, then attaches a previously created MSWord document to the fax.

lole_facsys = Create OLEObject
li_rc = lole_facsys.ConnectToNewObject("facsys.faxsession")
//Check for the return code
If li_rc <> 0 Then
   // ERROR CONDITION
   Messagebox("ERROR",li_rc)
   Destroy lole_facsys
   Return li_rc
End If
// log on to server (SERVER, ID, PASSWORD)
lole_facsys.logon("PRC00","00MNB","")
lole_faxmsg = lole_facsys.createmessage()
lole_faxmsg.text = 'Expedite / De Expedite Notice'
lole_recipient = lole_faxmsg.recipients.add()
lole_recipient.Name = 'Matt Balent'
lole_recipient.faxnumber = '18105555078'
// previously saved word doc
lole_attachmnt = lole_faxmsg.attachments.add('C:\vfax.doc')
lole_attachmnt.type = 14 // MS word
li_rc = lole_faxmsg.send()
destroy lole_attachmnt
destroy lole_recipient
destroy lole_faxmsg
lole_facsys.logoff()
destroy lole_facsys

This assumes you have Facsys installed on the user’s desktop and they have an account on the Facsys server AND that the Facsys server is set up to render the attachment. I used Facsys v 4.7 on the desktop.

]]>
Create Word Doc and attach to Outlook Email in PowerBuilder https://anvil-of-time.com/powerbuilder/create-word-doc-and-attach-to-outlook-email-in-powerbuilder/ Wed, 23 Sep 2009 15:47:43 +0000 http://anvil-of-time.com/2009/09/23/create-word-doc-and-attach-to-outlook-email-in-powerbuilder/ Here is some sample PowerScript from back in 2004. The word document created is saved from a template doc previously created with specific bookmarks used to format the text.

oleobject lole_word
OLEObject lole_item, lole_attach, lole_outlook
string ls_file_name
lole_word = CREATE oleobject
lole_outlook = Create OLEObject
TRY
	lole_word.connecttonewobject('word.application')
CATCH (runtimeerror a)
	Messagebox('Error','Error connecting with MS Word. Process terminating.')
	RETURN -1
END TRY
lole_word.visible = FALSE // dont want to see word<br />// get the Word template
ls_file = C:\temp\template.doc
If Not FileExists(ls_file) Then
	//ERROR CONDITION
	 li_rc = MessageBox("File Not Found", "Cannot find document template: "+ls_file+"~r"+ "Do you want to select the template?", Question!, YesNo!, 2)
	 If li_rc = 2 Then
		Return -1
	 Else
		GetFileOpenName("Select Template File", ls_file, ls_file_name, ".DOC","Template Files, *.DOC")
		If Not FileExists(ls_file_name) Then
			// ERROR CONDITION
			MessageBox("Template File Not Found","Cannot find document: "+ ls_file_name)
		   return -1
		End If
	 End If
ELSE
End If
TRY
	lole_word.Documents.open(ls_file_name)
CATCH (runtimeerror b)
	Messagebox('Error','Error opening ' + ls_file_name + ' with MS Word. Process terminating.')
	RETURN -1
END TRY
// put data into bookmarks on document
lole_word.activedocument.bookmarks.item('vendor').range.text = ids_xdex.getitemstring(1,'vendordesc') //data directly from datastore 
lole_word.activedocument.bookmarks.item('buyertext').range.text = ls_buyernote //data assigned previously to variable
lole_word.activedocument.bookmarks.item('text').range.text = ls_data
lole_word.activedocument.bookmarks.item('legend').range.text = '* D - De Expedite, E - Expedite, C - Cancel' //hard coded string data
lole_word.activedocument.bookmarks.item('buyer').range.text = ls_name
lole_word.activedocument.bookmarks.item('buyerfax').range.text = ls_fax
lole_word.activedocument.bookmarks.item('buyeremail').range.text = ls_email
is_doc = ls_file + ls_doc
// save document before processing further
TRY
	lole_word.activedocument.saveas(is_doc)
CATCH (runtimeerror c)
	Messagebox('Error','Error saving document ' + is_doc + '. Process terminating.')
	RETURN -1
END TRY
TRY
	lole_word.activedocument.saveas('P:\temp.doc') // save 'dummy' so 'real' document is not locked
CATCH (runtimeerror d)
	Messagebox('Error','Error saving temp document. Process terminating.')
	RETURN -1
END TRY
lole_word.activedocument.printout()	// print from word
//Connect to Outlook session using 'Outlook.Application'
li_rc = lole_outlook.ConnectToNewObject("outlook.application")
If li_rc &lt;&gt; 0 Then
	// ERROR CONDITION
          Messagebox("Outlook Error",string(li_rc))
          Destroy lole_outlook
          Return li_rc
End If
//Creates a new mail Item
lole_item = lole_outlook.CreateItem(0)
//Set the subject line of message
lole_item.Subject = "Expedite / De Expedite Notice"
//Body of mail message
lole_item.Body = "Please review the attached file and advise: "+Char(13)
//Recipient(s) Use a semicolon to separate multiple recipients
lole_item.To = dw_vendorfax.getitemstring(1,'contactemail')
lole_attach = lole_item.Attachments
lole_attach.add(is_doc) // attach the word document
lole_item.Display //displays the message
//    lole_item.Send //sends the message (commented out so user can personalize the message in Outlook if they want to)
lole_outlook.disconnectobject()
DESTROY lole_outlook
DESTROY lole_word

Updated March 2021

]]>