VB6 Interop of Function with Array of User Defined Type (UDT)

27. January 2013 03:33 by Mrojas in COM Interop  //  Tags: , , , , , , , , ,   //   Comments (0)

Well tonight while I was deleting some spam comments from my blog and watching Dr. Who with my wife, I found a rather interesting comment.

So the story was:

First there is a VB6 DLL that had a class called Class1 with code like the following:

public type emprecord 
name as string 
end type 

Public Sub Fn(T()as emprecord) 
 MsgBox "The silence is comming said Prisoner 0"
End Sub

When this little dll was called from a VB.NET big brother 

Dim test as new prj.class1   
Dim em(0) as prj.emprecord 'able to create it no problem 
em(0).name="hello" 
test.fn(em)  ' here gives error


An error ocurred... well this is not very document issue with the TLBIMP tool which creates the interop assemblies. See StackOverflow Answer. The workaround is to right click on your type library, select properties, and change Embed interop Types to false.

After that you will be able to call your function.




DefaultValue for Enum not working.

17. February 2012 18:13 by Mrojas in VB6 Migration  //  Tags: , , , , ,   //   Comments (0)

Today I was writting a custom control which had a custom property whose type was an EnumType.

I needed the DefaultVAlue attribute so Visual Studio.NET will not serialize the value to the container's code.

Usually in C# you can do something like:

[DefaultValue(TheEnum.TheValue)]
public TheEnum MyProperty
{
get { ... }
set { ... }
}
And that works. Well it does not work in VB.NET
To use an enum value for an Enumeration type in VB.NET you should write something like:
<DefaultValue(GetType(TheEnum), "TheValue")> _
Property MyProperty as TheEnum
   Get
       ...
   End Get
   Set 
        ...
   End Set
End Property
 

Add Return for COM Interop marshaling to Short in Vb.NET

3. February 2012 14:10 by Mrojas in COM Interop  //  Tags: , , , , , , , ,   //   Comments (0)

In VB.NET if you want to make your interfaces available thru COM and make sure that its parameters are of a certain type you have to use the MarshalAs attribute. For the return type it is a little tricky because it has to be added after the As Keyword.

 

<ComVisible(True)> _
<Guid("15D492C7-CD14-4239-B98D-689F329EEDA4")>
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface MyCOMInterface
	Function FooReturningShort(ByVal data As Integer, <MarshalAs(UnmanagedType.U2)> ByVal shortData As short) As <MarshalAsAttribute(UnmanagedType.U2)> Short
End Interface

How to add helpstring in tlb generate from VB.NET or C# class

25. January 2012 11:34 by Mrojas in COM Interop  //  Tags: , , , , ,   //   Comments (0)

In IDL you can have a helpstring attribute that adds descriptions to your interfaces.
To be able to generated those helpstring tags in a VB.NET  class you should use the Description tag

For example see this example taken from:http://stackoverflow.com/questions/6668500/getting-the-helpstring-attribute-applied-to-c-sharp-properties-exposed-via-com-i

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;



namespace ClassLibrary1 {
    [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IFoo {
        int property {
            [Description("prop")]
            get;
            [Description("prop")]
            set;
        }
    }
}

NOTE: remenber that in some cases you have to apply the description attribute to both get and set.

ADITIONAL NODE:

After some test, I found that there are some differences with the helpstring behaviour in VB.NET. For example the helpstring attribute is not generated for methods. More on C#/VB.NET Interop Differences http://mheironimus.blogspot.com/2006/10/vbnet-com-interface-issues.html

Quick replacement for the VB6 OLE Container Control in .NET

23. January 2012 13:09 by Mrojas in COM Interop  //  Tags: , , , , ,   //   Comments (0)

I have developed a very quick replacement for the OLE Container Control that you had in VB6.
I just did it in rush so it just supports basic properties as DisplayType Icon or Content
The content functionality is performed using the Vista feature for content preview. I would have tried using a 
WebBrowser control but in new versions of Office, the default is not showing the document on the Browser and
it might be difficult to change the registry in some vista or Win7 environments.

 The following picture show the OLEContainer inside a Windows Form.

This is the container with the Display set to content:

 

And the container with display set to icon:

 

You can call the CreateLink and you can also use the DoVerb Open.

I have attached the example source code in this post.

ReplaceOLEContainer.zip (100.69 kb)

UPDATE

 NOTE: I friend also sent me a link to this article in CodeProject which is very similar: http://www.codeproject.com/Tips/487566/OLE-container-surrogate-for-NET

 

NOTE: This solution only applies for read-only. If you wan to edit your files, then you need a real ActiveX container. MS used to have a sample OCX called DSOFramer that allows you to do that. Warning: this sample is no longer supported by MS becuase it said to have issues However I have used it in the past and it worked fine in some simple scenarios. I have added the control and its source to this post. There is a commercial product from Edraw http://www.edrawsoft.com/edword.php that is supported by them and has samples for C# and VB.NET

 

 NOTE: Another approach, embed the application in your windows form.

In general what you should do is use the SetParent, SetWindowLong and MoveWindow APIs to embed the application. Something like this:

var filename = openFileDialog1.FileName;
                var officeApplicationProgID = "Excel.Application";
                var officeApplicationType = Type.GetTypeFromProgID(officeApplicationProgID, false);
                dynamic officeApplication = Activator.CreateInstance(officeApplicationType);
                officeApplication.Workbooks.Open(filename);
                int pid = 0;
                GetWindowThreadProcessId(officeApplication.HWnd, out pid);
                officeApplication.Visible = true;
                //officeApplication.Visible = false;
                var process = Process.GetProcessById(pid);
                var panel = new Panel();
                panel.Location = new Point(0, 0);
                panel.Size = new Size(this.Size.Width, this.Size.Height);
                var processHandle = process.MainWindowHandle;
                SetParent(processHandle, panel.Handle);
                SetWindowLong(processHandle, GWL_STYLE, WS_VISIBLE + WS_MAXIMIZE + WS_CHILD);
                MoveWindow(processHandle, 0, 0, panel.Width, panel.Height, true);
                this.mainBody.Controls.Add(panel);
 
Figure: Example of technique of hosting Excel inside a Windows Form Application

I have attached a sample project. (Remember to free your resources, and close the excel App before closing your application, I skipped that from this sample)

DsoFramer.zip (463.42 kb)

ExampleOfEmbeddingExcelInWindowsForm.zip (53.76 kb)

Silverlight ObservableCollection with AddRange

Silverlight is when you have to add a lot of items.
I know, I know maybe you should choose another way to show that data,
but leaving philosophical-ui design discussions, the real problem is that
usually those components are bind  to ObservableCollections.

ObservableCollections are a bit of an exhibitionist.
Each time you add an item it will yell

Hey!! Yoo-hoo! HEY!!! YOU!!
I'm HEREEEEEEEEEEEEEEEEEE!!!!
Look at me!! Look at Me!!! Look Mom No Hands!!! Look Dad no Feet!!! HEY!!!!!!!!

So if you have some code like:

for(int i=0;i<10000;i++)
{
    comboItems.Add("item" + i);
}


A nice thing will be to be able to do something like:

var items = new String[10000] 
for(int i=0;i<10000;i++) 
{ 
    items[i]="item" + i; 
} 
comboItems.AddRange(items); 


And then provide just ONE notification of Collection Changed instead of a lot of
little cries for attention.

Well that is the reason for this new version of ObservableCollection that I call
RangeObservableCollection:

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace Utils
{
    public class RangeObservableCollection<T> : ObservableCollection<T>
    {
        private bool _suppressNotification = false;

        public RangeObservableCollection() : base() { }

        public RangeObservableCollection(IEnumerable<T> collection) : base(collection) { }

        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (!_suppressNotification) base.OnPropertyChanged(e);
        }

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (!_suppressNotification)
                base.OnCollectionChanged(e);
        }

        /// <summary>
        /// Adds a collection suppressing notification per item and just raising a notification
        /// for the whole collection
        /// </summary>
        /// <param name="list"></param>
        public void AddRange(IEnumerable<T> list)
        {
            if (list == null) throw new ArgumentNullException("list");
            _suppressNotification = true;
            foreach (T item in list)
            {
                Add(item);
            }
            _suppressNotification = false;
            OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
        }

    }
    
}

Property Pages in VB.NET and C#

13. September 2011 02:20 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

Visual Basic 6.0 property pages allow you to work around the
limitations of the Visual Basic Property Browser. For example,
you can use property pages to give users a way to add a collections of
colors to a color list user control.

In the property page you would write code that manages the collection,
something beyond the capabilities of the Visual Basic Property Browser.
In contrast, the Visual Basic .NET Property Browser can be used
to edit any .NET variable type or class. Property Pages are no longer needed.

The Upgrade Wizard and the VBUC do not automatically upgrade your
Visual Basic 6.0 property pages but they can sure be of help.
What if you really what to keep those property pages? Is there any workaround.
mmmm Sure there is.

You can follow these steps.

1. Before migrating your Visual Basic 6.0 project with the VBUC
modify your property pages (.pag) files to resemble common Visual Basic 6.0 forms.
For example a property page looks like this:

VERSION 5.00
Begin VB.PropertyPage PropertyPage1 
   Caption         =   "PropertyPage1"
   ClientHeight    =   3600
   ClientLeft      =   0
   ClientTop       =   0
   ClientWidth     =   4800
   PaletteMode     =   0  'Halftone
   ScaleHeight     =   3600
   ScaleWidth      =   4800
   Begin VB.TextBox Text1 
      Height          =   495
      Left            =   480
      TabIndex        =   1
      Text            =   "Text1"
      Top             =   1200
      Width           =   2175
   End
   Begin VB.CommandButton Command1 
      Caption         =   "Command1"
      Height          =   615
      Left            =   3120
      TabIndex        =   0
      Top             =   480
      Width           =   1455
   End
   Begin VB.Label Label1 
      Caption         =   "Label1"
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   600
      Width           =   1815
   End
End
Attribute VB_Name = "PropertyPage1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

Can be turned into a Form, to ease migration with simple changes:

VERSION 5.00
Begin VB.Form  PropertyPage1 
   Caption         =   "PropertyPage1"
   ClientHeight    =   3600
   ClientLeft      =   0
   ClientTop       =   0
   ClientWidth     =   4800
   PaletteMode     =   0  'Halftone
   ScaleHeight     =   3600
   ScaleWidth      =   4800
   Begin VB.TextBox Text1 
      Height          =   495
      Left            =   480
      TabIndex        =   1
      Text            =   "Text1"
      Top             =   1200
      Width           =   2175
   End
   Begin VB.CommandButton Command1 
      Caption         =   "Command1"
      Height          =   615
      Left            =   3120
      TabIndex        =   0
      Top             =   480
      Width           =   1455
   End
   Begin VB.Label Label1 
      Caption         =   "Label1"
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   600
      Width           =   1815
   End
End
Attribute VB_Name = "PropertyPage1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

If the form had a event like:

Private Sub PropertyPage_Initialize()

End Sub

Change that to Form_Load()

2. Rename the file from .pag to .frm.
3. Remove the .pag from the VB6 project
4. Add the .frm file to the VB6 project
5. Run the VBUC tool.

Once migrated you have a close migration of your original Property Page.
Remember however that some things change in .NET and you will need to manually finish some details.
For example, you need to review code in the
PropertyPage_ApplyChanges(),
PropertyPage_EditProperty(PropertyName As String)
PropertyPage_SelectionChanged().

Ok. Once you take your Property Page to .NET how do you integrate it with your control.
Well that’s easy. There you could create a ControlDesigner or just use an UITypeEditor.

Let’s see the UITypeEditor aproach.

The general idea with this aproach is to provide an UITypeEditor (this is just a way to provide an
editor in the property Browser that is not supported by default. And taking advantage of that editor
we will show the form that was produced after migrating out Property Pages. If you want an interface
more similar to what you had on Visual Basic 6.0 you can modify the property page and add a TabControl.

Ok. So these are the steps to follow:

1. First you need to create a type for which you will provide a Type Editor. We will call this type CustomData

 

namespace CustomEditor
{
    public class CustomData
    {
    }
}

2.Now we will add a property to our control.

        public CustomData Custom
        {
            get;
            set;
        }

3. Now add attributes to associate an editor

        [Description("Custom"), Editor(typeof(CustomDataEditor), typeof(UITypeEditor))]
        public CustomData Custom
        {
            get;
            set;
        }

4. And now lets implement the CustomDataEditor

using System.Windows.Forms.Design;

namespace CustomEditor
{
    public class CustomDataEditor : UITypeEditor
    {
        public CustomDataEditor() {}

        public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
        {

            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            IWindowsFormsEditorService frmsvr = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
            if (frmsvr == null) return null;
            PropertyPageForm propPage = new PropertyPageForm();
            propPage.control = (MyControl)context.Instance;
            frmsvr.ShowDialog(f);
            return null;
        }

    }
}

5. In the previous put attention to the highlighted details. What we are doing is getting a reference
to the WindowsFormEditor service so we property open the PropertyPage that has now been turned into a form.
It is important that you add a public property to the migrated PropertyPage like:

public MyControl control { get; set; };

because that will give you a reference to the actual control that is on the form. The property page is just an interface
you will need to set the values into properties on your control, in order for them to be serialized.

6. Once you do that, when you see the property editor for your control you will see something like:

When you press the … button it will show up your property page form.

You could also add other visual elements like:

Or

But that is maybe for another post.

Regards

Setting the Default Printer in .NET

 
The following C# code shows how to use WMI to query printers information, set and get default printer.
public bool SetDefaultPrinter()
{
    System.Management.ManagementObjectSearcher search = 
default(System.Management.ManagementObjectSearcher); System.Management.ManagementObjectCollection results =
default(System.Management.ManagementObjectCollection); System.Management.ManagementObject printer =
default(System.Management.ManagementObject); search =
new System.Management.ManagementObjectSearcher("select * from win32_printer"); results = search.Get(); //Get Default Printer System.Management.ManagementObject defaultPrinter = null; foreach (System.Management.ManagementObject foundPrinter in results) { System.Management.PropertyDataCollection
propertyDataCollection = foundPrinter.Properties; if ((bool)foundPrinter["Default"]) // DEFAULT PRINTER { System.Diagnostics.Debug.WriteLine(foundPrinter["Name"]); System.Diagnostics.Debug.WriteLine(foundPrinter["Location"]); } } //Sets new default Printer foreach (System.Management.ManagementObject foundPrinter in results) { System.Diagnostics.Debug.Print(foundPrinter["Name"].ToString()); if (foundPrinter["Name"].Equals("PDFCreator")) { System.Management.ManagementBaseObject outParams =
foundPrinter.InvokeMethod("SetDefaultPrinter", null, null); if (outParams == null) System.Diagnostics.Debug.WriteLine("Unable to set default printer"); Int32 retVal = (int)(uint)outParams.Properties["ReturnValue"].Value; if (retVal == 0) return true; else return false; } } return false; }

VB6 Windows Service

2. June 2011 04:30 by Mrojas in General  //  Tags: , , , , , , , ,   //   Comments (0)

How do you write a Windows Service in VB6?

Althought this is not a recommend practice due to the stability issues and VB6 support, if for
any reason you need to do this I provide a guide of how to do that in VB6 and how do the same thing
in VB.NET and C#.

Using the NTSVC.ocx

This is an OCX implement by Mauricio Ordonez some time ago.
It is very simple to use. You just drop it on a form and add some code to your VB6.

Example: VB6 Form with the NTSVC.ocx control

Private Sub Form_Load()
Me.Visible = False
    Dim strDisplayName As String

On Error GoTo Err_Load

    strDisplayName = NTService1.DisplayName
   
    If Command = "-install" Then
        ' Enable interaction with desktop.
        NTService1.Interactive = True
       
        If NTService1.Install Then
MsgBox strDisplayName & " installed successfully"
        Else
            MsgBox strDisplayName & " failed to install"
        End If
        End
   
    ElseIf Command = "-uninstall" Then
        If NTService1.Uninstall Then
            MsgBox strDisplayName & " uninstalled successfully"
        Else
            MsgBox strDisplayName & " failed to uninstall"
        End If
        End
   
    ElseIf Command = "-debug" Then
        NTService1.Debug = True
   
    ElseIf Command <> "" Then
        MsgBox "Invalid command option"
        End
   
    End If
   
    ' Connect service to Win32 services controller.
    NTService1.StartService
   
Err_Load:
    ' Error starting service
   
End Sub


Private Sub Timer1_Timer()
MsgBox "hola"
End Sub

 

NOTE: Remember that VB6 is not a supported platform and that even if it is true
that you can still run VB6 code in Windows Vista and Windows 7 MS does not support this
platform anymore.

How can I convert my VB6 service to .NET?

To create a Windows Service in VB.NET follow this steps.

1. First you need to create a Windows Service Project:

a. Open Visual Studio 2010

b. Go to the File\New\Project….

c. Select the Windows Service Template

d. And you just put your code in the OnStart method:

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Add code here to start your service. This method should set things
        ' in motion so your service can do its work.
        ExecuteWindowsServiceCode()
    End Sub

    Protected Overrides Sub OnStop()
        ' Add code here to perform any tear-down necessary to stop your service.
    End Sub

    Private Sub ExecuteWindowsServiceCode()
        'TODO Add Some Code
    End Sub

End Class
 

e. Another typical thing to do in Windows Service is to add a Timer Control, you you can have
your windows service performs some actions every number of seconds. To do that, drag a
Timer Control on your Service component, execute the Start method of the timer control on the OnStart method and
handle the Tick event:

 

 

 

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Add code here to start your service. This method should set things
        ' in motion so your service can do its work.
        Timer1.Start()
        ExecuteWindowsServiceCode()
    End Sub

    Protected Overrides Sub OnStop()
        ' Add code here to perform any tear-down necessary to stop your service.
        Timer1.Stop()
    End Sub

    Private Sub ExecuteWindowsServiceCode()
        'TODO Add Some Code
    End Sub

    Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
        MsgBox("Viva la vida Loca!")
    End Sub
End Class

If you need to recover some of your code from your VB6 project, download our Visual Basic Conversion Tool VBUC tool.

 

How do I Install my Windows Service?

In general you just need to use the command line utility installutil.exe for more details see this other post.

Converting from VB6 or Winforms to Context-Sensitive Help in Silverlight

If you were in VB6 HelpContextID will be familiar for you (http://msdn.microsoft.com/en-us/library/aa267690(v=vs.60).aspx). In those sweet VB6 days all you had to do was:

Private Sub Form_Load ()
   App.HelpFile = "VB.HLP"
   Frame1.HelpContextID = 21004   
   Text1.HelpContextID = 21001
   Form1.HelpContextID = 21005
End Sub

And each time you pressed the F1 button your application will have opened the .hlp file and show you the Help Topic corresponding to that ID. After migration from VB6 to WinForms Help you now have the HelpProvider.SetHelpKeyword http://msdn.microsoft.com/en-us/library/system.windows.forms.helpprovider.sethelpkeyword.aspx And you had to do something like:

internal System.Windows.Forms.HelpProvider HelpProvider1;
...
HelpProvider1.HelpNamespace = "sample.chm";
HelpProvider1.SetHelpKeyword(TextBox1, "1007.html");
HelpProvider1.SetHelpNavigator(TextBox1, HelpNavigator.Topic);
HelpProvider1.SetHelpKeyword(ListBox1, "1006.html");
HelpProvider1.SetHelpNavigator(ListBox1, HelpNavigator.Topic);

And all that seems nice. But, what can you do when you cross over to SilverlightjQuery15205164761650376022_1357918518660? Well, in general there are several systems that allow you to author your help files in html or convert your .hlp or .chm files to html, but how do you link your components to that help system in order to provide context-sensitive help???? Ok. So one of the possible solutions is very very simple. In general, the solution that I will show in this post is this: 1) First implement an attached property for adding a HelpKeyword to Silverlight components 2) Set the helpkeyword in the desired components 3) Provide logic that will open the appropiate help file. Ok. So let's implement a Silverlight Attached property. An attached propery is like adding a new property to your controls. This new attached property will be called Helpkeyword

using System;
using System.Windows.Shapes;
namespace System.Windows.Controls
{
    public class HelpProvider
    {
        public static readonly DependencyProperty HelpKeyword = 
            DependencyProperty.RegisterAttached("HelpKeyword", typeof(string), typeof(HelpProvider), new PropertyMetadata(null));
        public static void SetHelpKeyword(UIElement element, string keyword)
        {
            element.SetValue(HelpKeyword, keyword);
        }

        public static string GetHelpKeyword(UIElement element)
        {
            return (string)element.GetValue(HelpKeyword);
        }
    }
}

Ok. So once we have the attached property we have to use it, and set it on the code: To set it on the code we must add a namespace:

<UserControl x:Class="SilverlightApplication.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    ...    
    xmlns:help="clr-namespace:System.Windows.Controls"
    mc:Ignorable="d"
    ....>

And apply the attribute to components

<Button help:HelpProvider.HelpKeyword="helpforbutton1" Content="Button" ...  />
<TextBox help:HelpProvider.HelpKeyword="helpfortext1" Height="47" ... />

So that almost everything, now we just need to trigger the appropiate logic, to do that we will add a KeyUp handler to the top most element, in this example a grid. NOTE: if Silverlight is running on the browser F1 is not an option. I just used F2 here as an example.

<Grid x:Name="LayoutRoot" Background="White" Height="205" KeyUp="LayoutRoot_KeyUp">
<Button help:HelpProvider.HelpKeyword="helpforbutton1" ...  />
<TextBox help:HelpProvider.HelpKeyword="helpfortext1" ... />
</Grid>
 
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Browser;

namespace SilverlightApplication
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void LayoutRoot_KeyUp(object sender, KeyEventArgs e)
        {
            //check for the specific key. For now use F2 as the Help Shortcut
            if (e.Key==Key.F2) { 
                    var uielement = FocusManager.GetFocusedElement() as UIElement;
                    if (uielement!=null)
                    {
                        
                        var keyword = HelpProvider.GetHelpKeyword(uielement);
                        var host = HtmlPage.Document.DocumentUri.Host;
                        var port = HtmlPage.Document.DocumentUri.Port;
                        var url = string.Format("http://{0}:{1}/help/{2}.html", host,port,keyword);
                        HtmlPage.Window.Navigate(new Uri(url),"_blank");
                    }
            } // else ignore the keystroke
        }
    }
}
This property can be used on the IDE:

On code
var uielement = FocusManager.GetFocusedElement() as UIElement; 
if (uielement!=null) { 
var keyword = HelpProvider.GetHelpKeyword(uielement); 
}
This is an image of the application running.

And you can download the code from: CODE

If you have any questions or would like more info on Silverlight migration check www.silverlightmigration.com