.NET – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 17:35:59 +0000 en-US hourly 1 Sending iCalendar events via a .Net Assembly and SQLServer stored procedure https://anvil-of-time.com/ms-sql-server/sending-icalendar-events-via-a-net-assembly-and-sqlserver-stored-procedure/ Tue, 31 Jul 2012 22:01:12 +0000 http://anvil-of-time.com/wordpress/?p=1446 As I’ve mentioned in earlier posts, the iCalendar format is a standard way to send event information to a variety of email/scheduling systems (Outlook, Google, etc.). Here is a way to set it up so that you can simply call a stored procedure to send the event. This assumes you have SQL Server set up with access to Exchange.

In C# (I’m using Visual Studio 2008) create a class as indicated.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
using System.Net.Mail;
using System.Net.Mime;

namespace icalEmail
{
    public class StoredProcedures
    {
        [SqlProcedure]
        public static void SendiCal(DateTime startDateTime, DateTime endDateTime, string emailSubject,
           string userEmailAddress, string cnAttendee, string methodType, DateTime createdOn, string notes, string UID, string location, string seqNo, string smtpserver, int smtpport)
        {
            // create the iCal string in the proper format
            string iCal = CreateiCalFormat(startDateTime, endDateTime, emailSubject, userEmailAddress,
            cnAttendee, methodType, createdOn, UID, notes, location, seqNo);
            // create the MIME protocol Content-Type 
            var calendarType = new ContentType("text/calendar;method=REQUEST; charset=UTF-8");
            calendarType.Parameters.Add("method", "REQUEST");

            AlternateView caledarView = AlternateView.CreateAlternateViewFromString(iCal, calendarType);
            caledarView.TransferEncoding = TransferEncoding.SevenBit;
            // connect to smtpserver
            var client = new SmtpClient(smtpserver, smtpport);
            // create the email
            var mailMesage = new MailMessage();
            mailMesage.From = new MailAddress("donotreply@email.com");
            mailMesage.To.Add(new MailAddress(userEmailAddress));
            mailMesage.Subject = emailSubject;
            mailMesage.Body = iCal;
            mailMesage.AlternateViews.Add(caledarView);
            client.Send(mailMesage);
        }

        private static string _dateFormat = "yyyyMMddTHHmmssZ";
        private static string CreateiCalFormat(DateTime startDateTime, DateTime endDateTime,
        string emailSubject, string userEmailAddress, string cnAttendee, string methodType, DateTime createdOn, string UID,
        string notes, string location, string seqNo)
        {
            string iCal =

            "BEGIN:VCALENDAR" +

            "\nPRODID:-//SampleApp//AoT//EN" +

            "\nVERSION:2.0" +
            /// REQUEST for add or update, CANCEL for cancellation
            "\nMETHOD:" + methodType +

            "\nBEGIN:VEVENT" +

            "\nORGANIZER:CN='Matt'" +

            //"\nORGANIZER:MAILTO:matt.balent@anvil-of-time.com" +

            "\nATTENDEE;CN='" + cnAttendee + "'" +

            ";ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=" + userEmailAddress + 

            "\nDTSTART:" + startDateTime.ToUniversalTime().ToString(_dateFormat) +

            "\nDTEND:" + endDateTime.ToUniversalTime().ToString(_dateFormat) +

            "\nSTATUS:CONFIRMED" +

            "\nTRANSP:OPAQUE" +
            /// 0 for add, 1 for update or cancellation
            "\nSEQUENCE:" + seqNo +

            "\nUID:" + Guid.NewGuid() +
            /// resend original UID for updates and cancellations
            //"\nUID:" + UID +

            "\nDTSTAMP:" + createdOn.ToUniversalTime().ToString(_dateFormat) +

            "\nLAST-MODIFIED:" + createdOn.ToUniversalTime().ToString(_dateFormat) +

            "\nLOCATION:" + location +

            "\nDESCRIPTION:" + notes +

            "\nSUMMARY:" + emailSubject +

            "\nPRIORITY:5" +

            "\nCLASS:PUBLIC" +

            "\nEND:VEVENT" +

            "\nEND:VCALENDAR";

            return iCal;
        }

    }
}

This would be compiled into a .dll which needs to be registered in SQL Server. This assembly is to be marked as UNSAFE so someone with sysadmin priviges needs to register it.

CREATE ASSEMBLY [eventEmail]
AUTHORIZATION [dbo]
FROM 'C:\temp\icalemail.dll'
WITH PERMISSION_SET = UNSAFE

There is an interesting discussion of ‘UNSAFE’ assemblies on Stackoverflow.

Now create the stored procedure which will call the assembly.

create procedure dbo.usp_mb_eventnotify(@startDateTime datetime
				, @endDateTime datetime
				, @emailSubject nvarchar(80)
				, @emailSummary nvarchar(1000)
				, @location nvarchar(255)
				, @attendeeName nvarchar(100)
				, @attendeeEmail nvarchar(255)
				, @organizerName nvarchar(100)
				, @organizerEmail nvarchar(255)
				, @UID nvarchar(255)
				, @status nvarchar(12)
				, @requestType nvarchar(12)
				, @smtpserver nvarchar (255)
				, @smtpport bigint)
as external name [iCalEmail].[iCalEmail.Storedprocedures].[SendiCal];

Examples of use:

Create the event

exec dbo.usp_mb_eventnotify  
'2012-08-08 13:00:00',
'2012-08-08 15:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CONFIRMED',
'REQUEST',
'1234.na.corp.aot.com',
25

Update the event

exec dbo.usp_mb_eventnotify				
'2012-08-08 10:00:00',
'2012-08-08 12:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CONFIRMED',
'REQUEST',
'1234.na.corp.aot.com',
25

Cancel the event

exec dbo.usp_mb_eventnotify 
'2012-08-08 10:00:00',
'2012-08-08 12:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CANCELLED',
'CANCEL',
'1234.na.corp.aot.com',
25
]]>
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 – Accessing C# Classes via COM to Capture a Screenshot https://anvil-of-time.com/powerbuilder/powerbuilder-accessing-c-classes-via-com-to-capture-a-screenshot/ Wed, 13 Apr 2011 01:20:38 +0000 http://anvil-of-time.com/wordpress/?p=739 I was working with setting up screen capture functionality from within a PB11.5 application recently and came up against a wall which forced me to rethink my strategy. Initially I was using External Functions to the Windows gdi32.dll but had issues with my code causing it to ‘stop working’ after a few files were written. Things were working fine and then, bam!, zero byte files are being created. My assumption was that some memory was not being addressed/cleared correctly, blah, blah, blah.

I then decided to do the major work in .Net using C# since everything was being done behind the scenes (no visual components needed). I had the 2008 Express version loaded on my pc and with a little searching I had enough code samples to create my class. My C# class also uses gdi32 and some user32.dll function calls as well (similar to what I was attempting in PowerBuilder).

After creating a new Class Library project in Visual Studio and renaming a few things appropriately, the namespace/class declaration portion of the C# code looks like this:

namespace ScreenCapture
{
    [ComVisible(true)]

    [ClassInterface(ClassInterfaceType.AutoDual)]

    [ProgId("ScreenCapture.ScreenCapture")]
    public class ScreenCapture
    {

The three elements to pay particular attention to are the attributes (ComVisible, ClassInterface, and ProgId). You also need to make the project Com Visible by going to the properties of the project, selecting the Build Tab and checking the ‘Register for COM interop’ checkbox.

While in the project property window you should also sign the application which (among other things), assigns a GUID to it. Go to the Signing Tab and click on the ‘Sign the assembly’ checkbox. From the ‘Choose a strong name key file:’ dropdown choose, ‘New…’. Type in a keyfile name and uncheck the ‘Protect my key file with a password’ option then click ‘Ok’. When you save the project you will now have a .snk file associated with it.

The C# ‘ScreenShot.cs’ file is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;

namespace ScreenCapture
{
    [ComVisible(true)]

    [ClassInterface(ClassInterfaceType.AutoDual)]

    [ProgId("ScreenCapture.ScreenCapture")]
    public class ScreenCapture
    {
        ///
        /// Creates an Image object containing a screen shot of the entire desktop
        /// 

        public Image CaptureScreen()
        {
            return CaptureWindow(User32.GetDesktopWindow());
        }
        ///
        /// Creates an Image object containing a screen shot of a specific window
        /// 
        ///The handle to the window. 
        /// 
        public Image CaptureWindow(IntPtr handle)
        {
            // get te hDC of the target window
            IntPtr hdcSrc = User32.GetWindowDC(handle);
            // get the size
            User32.RECT windowRect = new User32.RECT();
			// size is entire screen
            User32.GetWindowRect(handle, ref windowRect);
            int width = windowRect.right - windowRect.left;
            int height = windowRect.bottom - windowRect.top;
            // create a device context we can copy to
            IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
            // create a bitmap we can copy it to,
            // using GetDeviceCaps to get the width/height
            IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
            // select the bitmap object
            IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
            // bitblt over
            GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
            // restore selection
            GDI32.SelectObject(hdcDest, hOld);
            // clean up 
            GDI32.DeleteDC(hdcDest);
            User32.ReleaseDC(handle, hdcSrc);
            // get a .NET image object for it
            Image img = Image.FromHbitmap(hBitmap);
            // free up the Bitmap object
            GDI32.DeleteObject(hBitmap);
            return img;
        }
        ///
        /// Captures a screen shot of a specific window, and saves it to a file
        /// 
        ///
        ///
        ///
        public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format)
        {
            Image img = CaptureWindow(handle);
            img.Save(filename, format);
        }
        ///
        /// Captures a screen shot of the entire desktop, and saves it to a file
        /// 
        ///
        ///
        public void CaptureScreenToFile(string filename, ImageFormat format)
        {
            Image img = CaptureScreen();
            img.Save(filename, format);
        }
        ///
        /// Called by external app to capture the screen and save it to the specified file 
        /// 
        ///Path and name of file
        ///Image format PNG, JPG, BMP, default GIF
        public void ExtCaptureScreenToFile(string filename, string sformat)
        {
            if (sformat == "PNG") 
            {
                CaptureScreenToFile(filename, ImageFormat.Png);
            }
            else if (sformat == "JPG")
            {
                CaptureScreenToFile(filename, ImageFormat.Jpeg);
            }
            else if (sformat == "BMP")
            {
                CaptureScreenToFile(filename, ImageFormat.Bmp);
            }
            else
            {
                CaptureScreenToFile(filename, ImageFormat.Gif);
            }
                        
        }

        ///
        /// Gdi32 API functions
        /// 
        private class GDI32
        {
            public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
            [DllImport("gdi32.dll")]
            public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
                int nWidth, int nHeight, IntPtr hObjectSource,
                int nXSrc, int nYSrc, int dwRop);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
                int nHeight);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteObject(IntPtr hObject);
            [DllImport("gdi32.dll")]
            public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
        }

        ///
        /// User32 API functions
        /// 
        private class User32
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
            [DllImport("user32.dll")]
            public static extern IntPtr GetDesktopWindow();
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowDC(IntPtr hWnd);
            [DllImport("user32.dll")]
            public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
        }

    }
}

After building the code in Visual Studio I went to PowerBuilder.

To use the component create the OLE object.

iole = CREATE OLEObject
// connect to C# class
li_rc = iole.connecttonewobject("ScreenCapture.ScreenCapture")
IF li_rc <> 0 THEN
	DESTROY iole
END IF

Then to use the component my code looks like this:

CHOOSE CASE is_filetype
	CASE '.jpg'
		ls_formatparm = 'JPG'
	CASE '.bmp'
		ls_formatparm = 'BMP'
	CASE '.png'
		ls_formatparm = 'PNG'
	CASE ELSE
		ls_formatparm = 'GIF'
END CHOOSE
// ls_fname specified previously
iole.ExtCaptureScreenToFile(ls_fname,ls_formatparm)

Pretty simple and straightforward.

]]>
VB.Net – “…any public member or cannot be found…” message https://anvil-of-time.com/net/vb-net-any-public-member-or-cannot-be-found-message/ Thu, 31 Mar 2011 01:20:03 +0000 http://anvil-of-time.com/wordpress/?p=734 So I’m playing around with Visual Basic in VisualStudio 2010 Express and I’m planning on importing some code from an old VB file I squirrelled away a while ago. I create my project and define a new class and then copy/paste out of the old code. Intellisence immediately squawks and I get the message “The Namespace or type specified in the Imports ‘System.Drawing’ doesn’t contain any public member or cannot be found.” Great.

A search in MSDN is fruitless but eventually I stumble upon a posting with exactily what I needed to know.

I need to add a reference to the class library. Go to Project > Add Reference and choose the .NET tab. Scroll down to the System.Drawing entry (or whatever you are using) and then add it.

]]>