Restoring simple lookup capabilities to Silverlight ListBox

27. January 2011 04:09 by Mrojas in General  //  Tags: , , , , , , ,   //   Comments (0)

VB6 and WinForms ListBox has the built in capability to provide a simple data look up. But the Silverlight ListBox does not.
So if you have a list with items:

Apple 
Airplane 
Blueberry 
Bee 
Car 
Zoo 
Animal Planet

And your current item is Apple when you press A the next current item will be Airplane

Apple 
Airplane 
Blueberry 
Bee 
Car 
Zoo 
Animal Planet

And the next time you press A the next current item will be Animal Planet

Apple 
Airplane 
Blueberry 
Bee 
Car 
Zoo 
Animal Planet

And the next time you press A the next current item will be Apple again

Ok to do in Silverlight you need to add a event handler. You can create a user control and this event handler and replace your listbox for your custom listbox or just add this event handler for the listboxes that need it. The code you need is the following:

void listbox1_KeyDown(object sender, KeyEventArgs e)
{
    String selectedText = this.listbox1.SelectedItem.ToString();
    String keyAsString = e.Key.ToString();
    int maxItems = listbox1.Items.Count;
    if (!String.IsNullOrEmpty(selectedText) && 
        !String.IsNullOrEmpty(keyAsString) && keyAsString.Length == 1 && 
         maxItems > 1)
    {   
        
        int currentIndex = this.listbox1.SelectedIndex;
        int nextIndex    = (currentIndex + 1) % maxItems;
        while (currentIndex != nextIndex)
        {
            if (this.listbox1.Items[nextIndex].ToString().ToUpper().StartsWith(keyAsString))
            {
                this.listbox1.SelectedIndex = nextIndex;
                return;
            }   
            nextIndex    = (nextIndex + 1) % maxItems;   
        }
        //NOTE: theres is a slight different behaviour because for example in 
        //winforms if your only had an item that started with A and press A the selectionIndex
        //will not change but a SelectedIndexChanged event (equivalent to SelectionChanged in Silverlight)
        //and this is not the Silverlight behaviour
    }
    
}

Determine is executable is a console application

30. November 2010 03:36 by Mrojas in General  //  Tags: , ,   //   Comments (0)

 

I you were looking for a way to do that just take a look at this post: http://weblogs.asp.net/whaggard/archive/2004/08/30/223020.aspx

Just notice that for this post you need to add this struct:

[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
  public IntPtr hIcon;
  public IntPtr iIcon;
  public uint dwAttributes;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
  public string szDisplayName;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
  public string szTypeName;
};

Interop: BinaryCompatibilty for VB6 Migrations

In VB6 when you have an ActiveX Library it was very important to use
the BinaryCompatibility setting to make sure that your applications did not break after a change.

So let’s first introduce what is binary compatibility and how to accomplish that in .NET.

Binary Compatibility allows to make changes to your components or COM classes without recompiling
every application you've made that uses the component.
And why do you need it. Why compatibility breaks.
On lets see.

An ActiveX Control or DLL expose Public interfaces.
Those interfaces have all of the properties, methods, events, etc. that you've marked as Public.
In other words, everything you've added that shows in Intellisense while working outside of your component.

Now let's say you have create a class, with two Methods Method1 and Method2

When you compile, VB generates all the COM infraestructure you need for your component.
It defines a CoClass and an interface and an entry for each method.

For a vb class with two methods:

Sub Method1()

End Sub

Sub Method2()

End Sub

It will produce a typelib like:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>
[
  uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
  version(1.0)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;

    [
      odl,
      uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030000)]
        HRESULT Method1();
        [id(0x60030001)]
        HRESULT Method2();
    };

    [
      uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
      version(1.0)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };
};

As you can see in the typelib there are IDs associated to each coclass, interface and
methods. Those IDs are the ones use when you generate the .exe file for your application.
Now if you modify your Class to:

 

Sub Method3()

End Sub

Sub Method4()

End Sub

Sub Method1()

End Sub

Sub Method2()

End Sub

and you use No Compatibility the typelib after your changes will be:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>

[
  uuid(FE5C56C2-E03A-4DC0-994D-B68543C72A46),
  version(1.0)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;

    [
      odl,
      uuid(A3032E1E-52FE-42E0-98FF-84A9DD4FD8C3),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030000)]
        HRESULT Method3();
        [id(0x60030001)]
        HRESULT Method4();
        [id(0x60030002)]
        HRESULT Method1();
        [id(0x60030003)]
        HRESULT Method2();
    };

    [
      uuid(72721504-CC56-4BB9-9447-C7193FE8C02D),
      version(1.0)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };
};

As you can see, now the ids for the methods, CoClass are different, so your applications will return errors like: Error 430 (Automation error, the component dies horribly) or Error 429 (can't create the object at all)

But if you instead used BinaryCompatibility then the typelib for your class will be:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>
[
  uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
  version(1.1)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;
    [
      odl,
      uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF),
      version(1.1),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030002)]
        HRESULT Method3();
        [id(0x60030003)]
        HRESULT Method4();
        [id(0x60030000)]
        HRESULT Method1();
        [id(0x60030001)]
        HRESULT Method2();
    };

    [
      uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
      version(1.1)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };

    typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
    _Class1 Class1___v0;
};

If you compare now the two typelibs you can see the Method1 and Method2 keep the same ids.

For each version a typedef is generated that will point to the last version. For example adding a Method5 will add new entry like:


    typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
    _Class1 Class1___v0;

    typedef [uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF), version(1.1), public]
    _Class1 Class1___v1;

Well that is what binary compatibility does. Now how to achieve binary compatibility in .NET

Binary Compatibility in .NET

Achieving binary compatibility in .NET is really easy. You just need to give more information to
make explicit how your typelib information will be. I will follow an approach as the one I already explained in this post:
http://blogs.artinsoft.net/mrojas/archive/2010/06/23/exposing-c-classes-thru-interop.aspx

Lets take our previous example:

using System;
using System.Runtime.InteropServices;

namespace InteropExamples
{
    public class Class1
    {
        public void Method3()
        {
        }
        public void Method4()
        {
        }
        public void Method1()
        {
        }
        public void Method2()
        {
        }
        public void Method5()
        {
        }
    }
}

In previous posts I had recommended using partial classes and using interfaces to explicitly specify what you what to be seen in COM. This means you start up with something like:

  public partial class Class1
    {
        public void Method3()
        {
        }
        public void Method4()
        {
        }
        public void Method1()
        {
        }
        public void Method2()
        {
        }
    }

    [ComVisible(true)]
    public interface _Class1
    {
        void Method3();
        void Method4();
        void Method1();
        void Method2();

    }
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(_Class1))]
    partial class Class1 : _Class1
    {
        #region _Class1 Members

        void _Class1.Method3()
        {
            Method3();
        }

        void _Class1.Method4()
        {
            Method4();
        }

        void _Class1.Method1()
        {
            Method1();
        }

        void _Class1.Method2()
        {
            Method2();
        }

        #endregion
    }

Now to make this code binary compatible then you have to make sure that the tlb file generated for your class is almost identical to that generated before. To acomplish that we must make sure that we your methods, interfaces and classes have the same guids and ids. Lets see how:

using System;
using System.Runtime.InteropServices;

namespace InteropExamples
{
    public partial class Class1
    {

        public void Method3()
        {
            System.Windows.Forms.MessageBox.Show("3 N");
        }

        public void Method4()
        {
            System.Windows.Forms.MessageBox.Show("4 N");
        }

        public void Method5()
        {
            System.Windows.Forms.MessageBox.Show("5 N");
        }


        public void Method1()
        {
            System.Windows.Forms.MessageBox.Show("1 N");
        }

        public void Method2()
        {
            System.Windows.Forms.MessageBox.Show("2 N");
        }
    }

    [ComVisible(true)] //This to make the interface Visible for COM
    [TypeLibType((TypeLibTypeFlags)((short)TypeLibTypeFlags.FHidden |
        (short)TypeLibTypeFlags.FDual |
        (short)TypeLibTypeFlags.FNonExtensible |
        (short)TypeLibTypeFlags.FOleAutomation))] //This to use the same flags as in previous tlb
    [Guid("9BAFD76D-8E6B-439C-8B6D-37260BFA3317")] //This is to make the class have the guid
    public interface _Class1
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();


    }

    [ComVisible(true)] //This to make the class Visible for COM
    [ClassInterface(ClassInterfaceType.None)] //This is to make sure that we have control on interface generation
    [ComDefaultInterface(typeof(_Class1))] //To set default interface
    [ProgId("Project1.Class1")] //To set ProgId 
    [Guid("C71C7AB0-552A-4D5D-A9FB-AF33830A697E")] //Maintain same Guid.
    partial class Class1 : _Class1, Class1___v0, Class1___v1
    {
        #region _Class1 Members

        void _Class1.Method3()
        {
            Method3();
        }

        void _Class1.Method4()
        {
            Method4();
        }

        void _Class1.Method1()
        {
            Method1();
        }

        void _Class1.Method2()
        {
            Method2();
        }

        #endregion


        #region Class1___v0 Members

        void Class1___v0.Method1()
        {
            Method1();
        }

        void Class1___v0.Method2()
        {
            Method2();
        }

        void Class1___v0.Method3()
        {
            Method3();
        }

        void Class1___v0.Method4()
        {
            Method4();
        }

        void Class1___v0.Method5()
        {
            Method5();
        }

        #endregion

        #region Class1___v1 Members

        void Class1___v1.Method1()
        {
            Method1();
        }

        void Class1___v1.Method2()
        {
            Method2();
        }

        void Class1___v1.Method3()
        {
            Method3();
        }

        void Class1___v1.Method4()
        {
            Method4();
        }

        void Class1___v1.Method5()
        {
            Method5();
        }

        #endregion
    }

    //This is to keep compatibility with old versions
    //we cannot generate a typedef so we will need to add all of the versions
    //for BinaryCompatibility
    [ComVisible(true)]
    [Guid("6B86684C-B3DD-4680-BF95-8DEE2C17AF5B")]
    [TypeLibType(TypeLibTypeFlags.FHidden)]
    public interface Class1___v0
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();
    }

    //This is to keep compatibility with old versions
    //we cannot generate a typedef so we will need to add all of the versions
    //for BinaryCompatibility
    [ComVisible(true)]
    [Guid("4A7A3317-BF13-443E-9DB0-2C5EA21F00CA")]
    [TypeLibType(TypeLibTypeFlags.FHidden)]
    public interface Class1___v1
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();
    }

}

Sadly in .NET you cannot use Interface Inheritance in COM. If there is interface inheritance YOU HAVE TO IMPLEMENT each interface. In the case of code that comes from VB6. VB6 just uses typedefs, so you really don’t know which methods belong to each version. So in the end all versions have all methods.

The other alternative to this method, is just to implement last version. And after generating the tlb, decompile it to an .IDL file add the typedefs and recompiled it. I explained something similar in this post:http://blogs.artinsoft.net/mrojas/archive/2010/05/17/interop-remove-prefix-from-c-enums-for-com.aspx

Ok. I hope this helps you to have an more clear idea of what Binary Compatibility is and how to do it in .NET. I am attaching some sample code. It show an ActiveX library that uses BinaryCompatibility and three version on an aplications that uses the different versions. And also a .NET class library that is equivalent to the VB6 one. HERE

Enjoy.

Exposing C# Classes thru Interop

23. June 2010 05:38 by Mrojas in General  //  Tags: , , , , , , , , ,   //   Comments (0)

Either if you migrate your application from VB6 to C# or if you develop a new application in C# something you end up with cases where you need to use your classes in legacy apps. Some of them could have been written in VB6 or could even be VBA macros in Excel applications.

Exposing your .NET classes can be sometimes very easy (you can think is just a matter of putting a ComVisible tag) but in other occasions is not that simple. Specially if your legacy application is using a lot of Late Bound calls like in VBA, so you must make sure that the COM information that you are exposing for your class is exactly what you really want and need.

OK. So I will provide some guidelines or some steps you should follow to provide a consistent COM interface for your .NET Code.

1. First you have to add the [ComVisible(true)]  attribute. Don’t think that’s all. Even if in some cases that is enough is better if you take an strict control of want is being generated for your class. Ok Let’s use the following class as an example:

using System;
using System.Runtime.InteropServices;

namespace InteropExamples
{
    [ComVisible(true)]
    public class MyVerySimpleClass 
    {
        public Class2 CreateANewClass()
        {  return new Class2()     }

        public int GetMyLuckyNumber() { return 15; }
    }
public class Class2 { 
  }
}

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
  uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
  version(1.0)
]
library ClassLibrary1
{
    // TLib :     // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
    importlib("mscorlib.tlb");
    // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _MyVerySimpleClass;

    [
      uuid(E03CCE68-2D55-3576-9DB6-019AAA667A5D),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
    ]
    coclass MyVerySimpleClass {
        [default] interface _MyVerySimpleClass;
        interface _Object;
    };

    [
      odl,
      uuid(D18BEEE1-4425-3AC7-891E-807EC2283731),
      hidden,
      dual,
      oleautomation,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")   

    ]
    interface _MyVerySimpleClass : IDispatch {
    };
};

In this case your class will be expose using all defaults. That is, a progId that will be the <AssemblyName>.ClassName an interface _<ClassName> is generated and the class is exposed only for IDispatch, which would not provide class information if you add the tlb reference to a VB6 or VBA project.

And if you run this code in VB6 you will have a problem like type mismatch when you try to use the method x.CreateAClass because it is returning an object that is not exposed thru COM.

Private Sub Command1_Click()
    Dim x As Object
    Set x = CreateObject("InteropExamples.MyVerySimpleClass")
    MsgBox x.GetMyLuckyNumber
    MsgBox x.CreateAClass
End Sub

So my recommendation is to make explicit what you want to expose. Maybe you only need some of the methods to be exposed. Well that is step two.

2. Define a public, ComVisible(true) interface that will define the methods that you want to be exposed thru COM. Sometimes it is better to implement the interface explicitly. I even recommend using partial classes so you isolate the COM stuff from your normal class. If you class is very simple you can leave all COM stuff there.

    //It is better to have an interface, because
    //you are completely sure what you are exposing or not
    [ComVisible(true)]
    public interface _MyVerySimpleClass
    {
        int GetMyLuckyNumber();
    }

3. (Recommedation) This is not an obligatory step but I recommend using partial classes.

    //Using partial classes allow you to separate all the
    //COM plumbing and leave your .NET implementation simple
    public partial class MyVerySimpleClass 
    {
        public Class2 CreateAClass()
        {
            return new Class2();
        }

        public int GetMyLuckyNumber() { return 15; }
    }

3. Make sure your partial class has the following attributes:

[ComVisible(true)] <—This is obvious because you want to use your class in COM

[ClassInterface(ClassInterfaceType.None)] <—This is because your want to take charge or what will be generated in your Typelib (tlb)

[ComDefaultInterface(typeof(_MyVerySimpleClass))] <—This is to indicate the interface that holds your COM visible methods.

[ProgId("InteropExamples.MyVerySimpleClass")] <—To establish which will be the progId not have a generated one
[Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")] <—this is important specially if you are trying to accomplish binary compatibility

Optionally add this attribute [IDispatchImpl(IDispatchImplType.CompatibleImpl)] this is currently marked as an obsolete attribute but it still works and I have found scenarios, specially in some VBA applications where you need this attribute in order to make some late bound calls.

4. And Explicitly implement the interface methods. This is important because some of the return values or arguments might need convertions. For example what can you do if your method returns a DataSet and your Excel VBA script is expecting something like a Recordset (more on this on other posts).

So now you will have a class like:

    //Using partial classes allow you to separate all the
    //COM plumbing and leave your .NET implementation simple
    public partial class MyVerySimpleClass
    {
        public Class2 CreateAClass()
        {
            return new Class2();
        }

        public int GetMyLuckyNumber() { return 15; }
    }

    //It is better to have an interface, because
    //you are completely sure what you are exposing or not
    [ComVisible(true)]
    public interface _MyVerySimpleClass
    {
        int GetMyLuckyNumber();
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)] //This is to make sure that no automatic generation of COM methods is done
    [ComDefaultInterface(typeof(_MyVerySimpleClass))] //This to explicitly establish which is the default interface
    [ProgId("InteropExamples.MyVerySimpleClass")]
    [Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")]
    [IDispatchImpl(IDispatchImplType.CompatibleImpl)]
    partial class MyVerySimpleClass : _MyVerySimpleClass
    {
    
        #region _MyVerySimpleClass Members
        //Explicit implementation is better because it avoids messing your .NET
        //class specification. Sometimes when you expose thru COM you can have problem with
        //methods overloads. For example you have to have the same method name but differente 
        //return type. Or you have a collition with an existing member.
        int _MyVerySimpleClass.GetMyLuckyNumber()
        {
            return GetMyLuckyNumber();
        }

        #endregion
    }

And your TLB is now explicit and exposes ONLY what you really really want.

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>

[
  uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
  version(1.0)
]

library ClassLibrary1
{
   // TLib :     // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
    importlib("mscorlib.tlb");
   // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _MyVerySimpleClass;

    [
      odl,
      uuid(80D00C45-EE10-3D65-A5FF-42AB7D8F8A71),
      version(1.0),
      dual,
      oleautomation,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples._MyVerySimpleClass")   

    ]
    interface _MyVerySimpleClass : IDispatch {
        [id(0x60020000)]
        HRESULT GetMyLuckyNumber([out, retval] long* pRetVal);
    };

    [
      uuid(029D468C-8BE6-498F-8A57-3B4B0306BA41),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
    ]

    coclass MyVerySimpleClass {
        interface _Object;
        [default] interface _MyVerySimpleClass;
    };
};

For more info about BinaryCompatibility see my other posts on Interop.

Which App.Config file will my ServiceComponent load?

30. April 2010 04:51 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

When a VB6 COM+ Component is migrated to a ServiceComponent,
you might want to take advantage of the Configuration files of .NET to specify your
connection strings and other important information.

So where should your App.Config go.

There is a slight diference with a ServiceComponent.
Remember that for a ServicedComponent the hosting process is ‘dllhost.exe’.
So your programs will look for config files in %windir%\System32, which is not a very nice solution.

You can instead set the ‘Application Base Directory’ of the COM+ Application.

Follow these steps:

1) Create an application.manifest file and copy it to the directory
that will be used as the base directory for the COM+ application. The file can be like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"/>

 

2) Create an app.config file and copy that file to the same :

<?xml version="1.0" encoding="utf-8" ?> 
<configuration>
<appSettings>
<add key="ConfigData" value="My Custom AppSetting!" />
</appSettings>
</configuration>
3) Configure the COM+ Application:
3.1) Open the Component Services MMC
3.2) Find the COM+ Application
3.3) Right Click the Application and go to Properties and Activation Tab
3.4) Find option: ‘Application Root Directory’
3.5) Write the path where the other two files where created.
 

This blog post was created from an original Blog Post from HeikkiRi.

AutoCAD VBA Migration to VB.NET or C#

AutoCAD 2010 will not be supporting VBA.

Quoting

“If you utilize VBA macros in your work environment, they will no longer work unless the VBA module is installed on your system. “
“When you run a command that requires VBA, a message dialog box will be displayed stating that VBA is no longer installed with AutoCAD and directing you to a website where you can download the VBA module. “

And also you can see that Autodesk states: “Autodesk is evaluating how long VBA will be supported in Autodesk products in the future. Though supported in the AutoCAD 2010-based products, it may or may not be supported in future releases. Therefore, it is strongly recommended that VB developers develop all new code using VB .NET.

VBA does not support 64bit systems in a native way.

But If you want some advice from the VB migration experts or help on your migration project from VBA to VB.NET or C# you can us contact Artinsoft Migration Services.

We build the VB Upgrade Wizard that shipped with Visual Studio and have been doing VB migrations for years.

DataGridView does not show Horizontal scrollbar

16. February 2010 10:25 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

During a migration from a FlexGrid to a DataGridView, we encountered a situation where the HorizontalScrollBar did not show.

I found many suggestions like setting a MinimumColWidth value for all columns, etc.

But it wasn’t until my friend Jesus added a line like:

 

mygrid.DockStyle = DockStyle.Fill

that the HorizontalScrollBar appear.

It might just be that the grid was too big for form but just for the record this is a possible solution.

Get Exact Text Width C#

15. December 2009 08:13 by Mrojas in   //  Tags: , , , , , , , , ,   //   Comments (0)

 I was recently trying to get the exact width of a string. And I found that the Graphics.MeasureString does not give an exact result.

I finally found Pierre Arnaud

post in Code Project, which gave me a good explaination and solution of what was happening.

You can see in the image Pierre put in his post:

 That Graphics.measurestring will return a size that might be bigger that the actual drawn size, this is due some GDI+ details that he explains in that post.

I really like the second proposed solution:

static public int MeasureDisplayStringWidth(Graphics graphics, string text,Font font)
{
    System.Drawing.StringFormat format  = new System.Drawing.StringFormat ();
    System.Drawing.RectangleF   rect    = new System.Drawing.RectangleF(0, 0,1000, 1000);
    System.Drawing.CharacterRange[] ranges  = { new System.Drawing.CharacterRange(0, text.Length) };
    System.Drawing.Region[]         regions = new System.Drawing.Region[1];

    format.SetMeasurableCharacterRanges (ranges);
    regions = graphics.MeasureCharacterRanges (text, font, rect, format);
    rect    = regions[0].GetBounds (graphics);

    return (int)(rect.Right + 1.0f);
}

 

 

ActiveX Server Migration to .NET

14. December 2009 08:01 by Mrojas in General  //  Tags: , , , , , , , , , ,   //   Comments (0)

 In VB6 ActiveX-EXEs or ActiveX OLE Server where used for several reasons. Sometimes it was performance (because it allowed you to run your code in another process) and sometimes as way to share resources between several applications, like connection information, database connections, mainframe info, etc.

During migration some of this ActiveX-Exes can be migrated as simple Assembly DLLs, but other require more attention. Specially if they have global variables that hold state shared by several programs.

In that is your case what are your options then?

1. Convert those ActiveX-Exes to Windows Services.

This option is simple. You modify your migrated assembly to work as a Windows Service. The easier way to do that is:

a) Start Microsoft Visual Studio 2005\2008

b) Go to File\New…\Project… and Select Windows Service

That will generated code like:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
namespace WindowsService1
{
   public partial class Service1 : ServiceBase
   {
      public Service1()    InitializeComponent();    }
     
protected override void OnStart(string[] args)    {   }
     
protected override void OnStop()    {   }
   }
}

c) Add a reference to the Remoting Assemblies: System.Runtime.Remoting;

d) Modify the previous code:

Add two using statements like:

using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;

Add a simple event log for tracing:

 

private static EventLog evt = new EventLog(“Application”);
private static string SVC_NAME = “ActiveX Server Example Svc”;

 And modify the OnStart and OnStop methods to look like:

  protected override void OnStart(string[] args)
{
    HttpChannel chnl = new HttpChannel(1234);
   
ChannelServices.RegisterChannel(chnl,true );
   
RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyClass), “MyClass.soap”, WellKnownObjectMode.Singleton);
   evt.WriteEntry(SVC_NAME + ” Started”);

}

protected override void OnStop() { evt.WriteEntry(SVC_NAME +” Stoppped”); }

Also make sure that MyClass extends MarshalByRefClass

2. Convert those ActiveX-Exes using the Artinsoft ActiveX migration helpers.

 Sometimes, you need your migrated application to replicate some of the original ActiveX EXE \OLE DLL VB6 characteristics. For example you need your ActiveX-EXE to start just when the first instance is created and to resemble the VB6 logic for Process creation\destruction.

For that purpose Artinsoft has created some helpers that our migration tool is able to automatically use in the generated code if it detects that this functionality is needed.

The code will then be changed from:

Dim myInstance As New MyProject.MyClass

To the following Helper method:

myInstance = MyProjectFactory.Create< MyProject.MyClass>(myInstance);

 And destroy calls can be changed to the following Helper method:

 myInstance= MyProjectFactory.Dispose<MyProject.MyClass >( myInstance); 

The migration tool will modify your ActiveX-EXEs or OLE Servers to be Windows EXE and the helper will then locate the assembly that contains the desired Class, create an instance and initilize a Remoting channel to the desired classes. Settings as SingleUse and MultiUse are also taken care by the helpers.

3. Other possible alternatives are using WFC and COM+ that I will comment in another post.

 

 

 

 

Return argument has an invalid type

10. December 2009 07:39 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

When you develop applications with remoting, or in some COM + Remoting scenarios, you could start founding very interesting exceptions.

We had a very unconfortable one. We had an ActiveX that is used in an intranet Web Page, that uses remoting to instanciate some classes in the local network.

When we runned outside of the IE, everything seem to work, but running in IE it produced an exception like:

Error : Return argument has an invalid type.
Type  : System.InvalidCastException
Source: mscorlib
Source: at System.Runtime.Remoting.Proxies.RealProxy.ValidateReturnArg(Object arg, Type paramType)
at System.Runtime.Remoting.Proxies.RealProxy.PropagateOutParameters(IMessage msg, Object[] outArgs, Object returnValue)
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)

Why??? Well what happens is simple, it is having an assembly resolution problem, it is not being able to resolve the type.

We solve the problem adding something like:

1. Find a place in your code to add an event like this (it could be in the Main of your program for example):

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
 
2. Add a handler like this: 

static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  
System.Reflection.Assembly assembly = null; 
   try
   {
        
assembly = System.Reflection.Assembly.Load(new System.Reflection.AssemblyName(args.Name));
   }
   catch (Exception ex)
  
      
System.Diagnostics.Trace.WriteLine(
            string.Format(“Problem with resolution of {0} : {1} {2}”, args.Name, ex.Message, ex.StackTrace));
   }
   return assembly;
}

Well, this worked for us, and I hope that helps you out.

 

Code compiles but System.IO.FileNotFound Exception is thrown

2. October 2009 10:04 by Mrojas in General  //  Tags: , , , , , , ,   //   Comments (0)

We have seen a situation that can be a little annoying.
I usually find that situation in ASP.NET projects, but it can also happen in Winforms.

The issue is like this:

  • You have a VStudio Solution
  • You have added all the needed references
  • All your code compiles

 

BUT

When you run the application it terminates at a certain point with a
FileNotFound exception indicating that the assembly blablabla  or one of its
dependencies could not be loaded.

 

Whats hapenning!!!

Do this:

1) Look at the bin\debug or bin\release and make sure that the assembly is in those directories.

2) If the assembly is not there, then go to the project references in the solution explorer, right click and select properties and set the Copy Local Setting

 

image

IE Explorer and Favorites folder or Special Folders in general

29. September 2009 04:05 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

I found this email in my inbox today:

Hi Mauricio,I came across a reference to your blog at :http://stackoverflow.com/questions/1286746/c-open-link-in-new-tab-webbrowser-control

I have been studying your writings on extending the WebBrowser control, and verified that the extended web code you wrote for C# compiles and works fine in VS 2010 beta, against FrameWork 4.0.

Many thanks for the valuble code and writing !

I am "stuck" on how to read the contents of an IE browser page when the page is displaying a local file, like the contents of the Favorites folder.

All my attempts to get at the Document or DomDocument by casting it to the usual mshtml.dll interfaces fail.

I am NOT asking you to answer my question, or respond, but if you ever get interested in blogging about this aspect of use of IE, I think many people would be interested.

I have done a lot of research on the net, and posted my own question on StackOverFlow : so far not one real pointer, and, possibly, this is not "doable" (?) : maybe what you are seeing when IE shows a file contents is a kind of "virtual explorer" view that is not parseable.

best, Bill xxxxxx”

And I decided to take at look at it to see if I could be of any help and I found out that it is easy and doable.

So I find an useful link by Andreas M. if you want to look at it.

In general My Favorites, Desktop, etc are special folder. So they need a trick to be able to access them.

 

image

Take the code from my ExtendedWebBrowser sample published in http://blogs.artinsoft.net/mrojas/archive/2009/05/01/opening-popup-in-a-newwindow.aspx

and http://blogs.artinsoft.net/mrojas/archive/2009/08/07/newwindow3.aspx and

1. Add a reference to %windir%\system32\shell32.dll

2. Add a new property to the ExtendedWebBrowser like:

    /// <summary>
    /// Returns the shell folderview object displayed in the webbrowser control.
    /// </summary>
    public Shell32.IShellFolderViewDual2 FolderView
    {
        get
        {
            return ((SHDocVw.WebBrowser)base.ActiveXInstance).Document
                     as Shell32.IShellFolderViewDual2;
        }
    }

And now you can access the special folder from your code. As Bill mentioned, that “page” or “special page” is not real HTML and not parseable but you can examine its contents for example you can do something like:

        /// <summary>
        /// Button 1_ click
        /// </summary>
        private void button1_Click(object sender, EventArgs e)
        {
            Shell32.IShellFolderViewDual2 specialFolder = this.extendedWebBrowser1.FolderView;
            string folderName = specialFolder.Folder.Title;
            string parentFolder = specialFolder.Folder.ParentFolder.Title;
            foreach (Shell32.ShellFolderItem f in specialFolder.Folder.Items())
            {
                if (f.IsFolder)
                    System.Diagnostics.Debug.WriteLine("Folder:" + f.Name);
                else
                    System.Diagnostics.Debug.WriteLine("File:" + f.Name);
            } // foreach
        } // button1_Click(sender, e)

Extended WebBrowser Control Series: WPF WebBrowser and the NewWindow2

To be able to catch popup windows and open them in your own window you
have to manage WebBrowser events like NewWindow2.

But how do you do that in WPF?

Well it isn’t really that difficult. These are the steps that you have to follow:

1. Add a COM reference to a reference to %windir%\system32\shdocvw.dll

2. Add a new CodeFile to your project. Lets say CodeFile1.cs And put this code:

using System;
using System.Runtime.InteropServices;
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
internal interface IServiceProvider
{

[return: MarshalAs(UnmanagedType.IUnknown)] 

object QueryService(ref Guid guidService, ref Guid riid);

}


3. To make an easy example. Lets assume we have a very simple window like:

 

And in that form we need some code like this:

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Guid SID_SWebBrowserApp = 
new Guid("0002DF05-0000-0000-C000-000000000046"); IServiceProvider serviceProvider =
(IServiceProvider)myWebBrowser.Document; //<—It seams that you need to
// navigate first to initialize this Guid serviceGuid = SID_SWebBrowserApp; Guid iid = typeof(SHDocVw.IWebBrowser2).GUID; //Here we will get a reference to the IWebBrowser2 interface SHDocVw.IWebBrowser2 myWebBrowser2 =
(SHDocVw.IWebBrowser2)
serviceProvider.QueryService(ref serviceGuid, ref iid);
            //To hook events we just need to do these casts
            SHDocVw.DWebBrowserEvents_Event wbEvents = 
(SHDocVw.DWebBrowserEvents_Event)myWebBrowser2; SHDocVw.DWebBrowserEvents2_Event wbEvents2 =
(SHDocVw.DWebBrowserEvents2_Event)myWebBrowser2;
            //Adding event handlers is now very simple
            wbEvents.NewWindow += 
new SHDocVw.DWebBrowserEvents_NewWindowEventHandler(wbEvents_NewWindow);
            wbEvents2.NewWindow2 += 
new SHDocVw.DWebBrowserEvents2_NewWindow2EventHandler(wbEvents2_NewWindow2); } void wbEvents2_NewWindow2(ref object ppDisp, ref bool Cancel) { //If you want make popup windows to open in your own window
// you need to assign the ppDisp to the .Application of
// the WebBrowser in your window
            Window1 wnd = new Window1();
            wnd.Show();
//Just navigate to make sure .Document is initilialized wnd.myWebBrowser.Navigate(new Uri("about:blank"));
Guid SID_SWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046"); IServiceProvider serviceProvider = (IServiceProvider)wnd.myWebBrowser.Document; Guid serviceGuid = SID_SWebBrowserApp; Guid iid = typeof(SHDocVw.IWebBrowser2).GUID; SHDocVw.IWebBrowser2 myWebBrowser2 = (SHDocVw.IWebBrowser2)serviceProvider.QueryService(ref serviceGuid, ref iid); ppDisp = myWebBrowser2.Application; } void wbEvents_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed) { MessageBox.Show(URL); } private void button2_Click(object sender, RoutedEventArgs e) { myWebBrowser.Navigate(new Uri("file://D:/MyProjects/ExtendedBrowserExample_v2/test0.htm")); }

Now you can manage your popupwindows:

 

You can download the test application from HERE

Where is my DXCore

19. August 2009 05:06 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

I’m an enthusiastic user of DXCore and I have been working on some extensions of my own. But I could not find the DXCore or DevExpress menu.

Well there is a hack for that.

Please invoke the Registry editor, add the "HideMenu" DWORD value to the following Registry key, and set its Value to 0:
HKEY_LOCAL_MACHINE\SOFTWARE\Developer Express\CodeRush for VS\9.1
This should make the "DevExpress" menu visible.

Look at the post for more details and keep enjoying DXCore.

C# PInvoke out or ref??

17. August 2009 09:40 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

If I have a PInvoke call like the following:

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename, 
   out UInt32 status);

What is the difference between

 

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, out UInt32 status);

and

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, ref UInt32 status);

Well, as long as I have tested it, they exactly the same. From the MSDN you can even read

“I could have selected the ref keyword here as well, and in fact both result in the same machine code at run time. The out keyword is simply a specialization of a by-ref parameter that indicates to the C# compiler that the data being passed is only being passed out of the called function. In contrast, with the ref keyword the compiler assumes that data may flow both in and out of the called function.”

“When marshaling pointers through P/Invoke, ref and out are only used with value types in managed code. You can tell a parameter is a value type when its CLR type is defined using the struct keyword. Out and ref are used to marshal pointers to these data types”

So what should you use? Well using the out keyword for PInvoke will just add some information or documentation to your method, but because these functions are implemented in C or C++ they might treat an out parameter as an IN parameter so I really prefere to use ref when I’m calling functions with PInvoke.

Migration from .NET Framework 1.1

13. August 2009 18:07 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

.NET has been around for quite a while. According Wikipedia it has been around since on 3 April 2003
So now there exist applications developed for .NET Framework 1.0 or 1.1 and people
need to migrate them to Framework 2.0 or Framework 3.5.

It is the general impression that there is not a direct path to 3.5.
As Zain Naboulsi explains in his blog you can go from 1.1 to 2.0 then from 2.0 to 3.5.
And From 2.0 to 3.5 the migration is a no-brainer because, both, 3.0 and 3.5 are based on 2.0.

A good reference also is the post of Peter Laudati on migration from 1.1 to 2.0.
Note: Peter’s post seem to have a broken link to the microsoft document about breaking changes in 2.0.
The correct link is this.

A more recent post by The Moth provides more links to breaking changes documents:

- Design time Breaking Changes in .NET Framework 2.0
- Runtime Breaking Changes in .NET Framework 2.0
- Microsoft .NET Framework 1.1 and 2.0 Compatibility
- Compatibility Testing Scenarios

Going from 1.1 to 2.0 or 3.5 can be just as simple as opening the solution in VS and compile
or it can take a lot of effort. Web Projects then to be more difficult due to several changes in ASP.NET.

So good luck.

Tools?

Well there a lot of static analyisis tools we have used
(some internal, some from Third Parties. I particulary like Understand and NDepend)

Extended WebBrowser Control Series: NewWindow3

Recently an user of the ExtendedBrowser v2 commented that he needed access to the NewWindow3 event.

The NewWindow3 event is raised when a new window is to be created. It extends NewWindow2 with additional information about the new window. 

Syntax

Private Sub object_NewWindow3( _
	ByRef ppDisp As Object, _
	ByRef Cancel As Boolean, _
	ByVal dwFlags As Long, _
	ByVal bstrUrlContext As String, _
	ByVal bstrUrl As String)

Parameters

object Object expression that resolves to the objects in the Applies To list. ppDisp Object expression that, optionally, receives a new, hidden WebBrowser or InternetExplorer object with no URL loaded. Cancel A Boolean value that determines whether the current navigation should be canceled. true Cancel the navigation. false Do not cancel the navigation. dwFlags The flags from the NWMFenumeration that pertain to the new window.

typedef enum NWMF 
{     
    NWMF_UNLOADING = 0x00000001,
     NWMF_USERINITED = 0x00000002,
     NWMF_FIRST = 0x00000004,
     NWMF_OVERRIDEKEY = 0x00000008,
     NWMF_SHOWHELP = 0x00000010,
     NWMF_HTMLDIALOG = 0x00000020,
    NWMF_FROMDIALOGCHILD = 0x00000040,
     NWMF_USERREQUESTED = 0x00000080,
     NWMF_USERALLOWED = 0x00000100,
     NWMF_FORCEWINDOW = 0x00010000,
     NWMF_FORCETAB = 0x00020000,
     NWMF_SUGGESTWINDOW = 0x00040000,
     NWMF_SUGGESTTAB = 0x00080000,
     NWMF_INACTIVETAB = 0x00100000
} NWMF;

bstrUrlContext The URL of the page that is opening the new window. bstrUrlThe URL that is opened in the new window.

Please notice:

Note   The NewWindow3 event is only fired when a new instance of Internet Explorer is about to be created. Calling showModalDialog or showModelessDialog does not trigger an event because they are not new instances of Internet Explorer. They are implemented as MSHTML host windows, which allows them to render and display HTML content but not hyperlinks between documents.

You can download from here

DOWNLOAD CODE HERE v3_1

ExtendedBrowserExampleVBNET.zip (92.56 kb)

CapsLock, NumLock in C# and VB.NET

10. July 2009 12:49 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

I was looking for a “.net” way of detecting the CapsLock state, but almost all the references pointed to pinvoke code like:

<DllImport("user32.dll")> _
Public Shared Function GetKeyState(VirtKey As Integer) As Integer
End Sub

And I finally found two ways:

1) You can call methods from the System.Console class:

You can use the System.Console.CapsLock property and if you want the NumLock state use: System.Console.NumberLock

or

2) You can call make an instance of Microsoft.VisualBasic.Devices.Keyboard. (For this if you are in C# you need to add a reference to Microsoft.VisualBasic.dll)

For example:

Microsoft.VisualBasic.Devices.Keyboard key = new Microsoft.VisualBasic.Devices.Keyboard();

and use properties like:

key.CapsLock

key.NumLock

key.ScrollLock

key.ShiftKeyDown

key.CtrlKeyDown

key.AltKeyDown

Printing RichTextBox contents in C#

10. July 2009 05:54 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

This post discusses and provides the implementation of a helper class to add support
for printing the formatted contents of a richtextbox control.

The print model in .NET is a little different than one used in VB6.

For example see: http://support.microsoft.com/kb/146022

Public Sub PrintRTF(RTF As RichTextBox, LeftMarginWidth As Long, _
   TopMarginHeight, RightMarginWidth, BottomMarginHeight)
   Dim LeftOffset As Long, TopOffset As Long
   Dim LeftMargin As Long, TopMargin As Long
   Dim RightMargin As Long, BottomMargin As Long
   Dim fr As FormatRange
   Dim rcDrawTo As Rect
   Dim rcPage As Rect
   Dim TextLength As Long
   Dim NextCharPosition As Long
   Dim r As Long

   ' Start a print job to get a valid Printer.hDC
   Printer.Print Space(1)
   Printer.ScaleMode = vbTwips

   ' Get the offsett to the printable area on the page in twips
   LeftOffset = Printer.ScaleX(GetDeviceCaps(Printer.hdc, _
      PHYSICALOFFSETX), vbPixels, vbTwips)
   TopOffset = Printer.ScaleY(GetDeviceCaps(Printer.hdc, _
      PHYSICALOFFSETY), vbPixels, vbTwips)

   ' Calculate the Left, Top, Right, and Bottom margins
   LeftMargin = LeftMarginWidth - LeftOffset
   TopMargin = TopMarginHeight - TopOffset
   RightMargin = (Printer.Width - RightMarginWidth) - LeftOffset
   BottomMargin = (Printer.Height - BottomMarginHeight) - TopOffset

   ' Set printable area rect
   rcPage.Left = 0
   rcPage.Top = 0
   rcPage.Right = Printer.ScaleWidth
   rcPage.Bottom = Printer.ScaleHeight

   ' Set rect in which to print (relative to printable area)
   rcDrawTo.Left = LeftMargin
   rcDrawTo.Top = TopMargin
   rcDrawTo.Right = RightMargin
   rcDrawTo.Bottom = BottomMargin

   ' Set up the print instructions
   fr.hdc = Printer.hdc   ' Use the same DC for measuring and rendering
   fr.hdcTarget = Printer.hdc  ' Point at printer hDC
   fr.rc = rcDrawTo            ' Indicate the area on page to draw to
   fr.rcPage = rcPage          ' Indicate entire size of page
   fr.chrg.cpMin = 0           ' Indicate start of text through
   fr.chrg.cpMax = -1          ' end of the text

   ' Get length of text in RTF
   TextLength = Len(RTF.Text)

   ' Loop printing each page until done
   Do
      ' Print the page by sending EM_FORMATRANGE message
      NextCharPosition = SendMessage(RTF.hWnd, EM_FORMATRANGE, True, fr)
      If NextCharPosition >= TextLength Then Exit Do  'If done then exit
      fr.chrg.cpMin = NextCharPosition ' Starting position for next page
      Printer.NewPage                  ' Move on to next page
      Printer.Print Space(1) ' Re-initialize hDC
      fr.hdc = Printer.hdc
      fr.hdcTarget = Printer.hdc
   Loop

   ' Commit the print job
   Printer.EndDoc

   ' Allow the RTF to free up memory
   r = SendMessage(RTF.hWnd, EM_FORMATRANGE, False, ByVal CLng(0))
End Sub

The VBCompanion provides excellent helpers that provide a lot fo the VB6 Printer object functionality, so you dont have to change any of your actual code, but in some cases, you might just want to remove that code, specially for very specific things like printing a RichTextBox.

So here I’m providing a .NET simplified helper that allows you to print the contents of a RichTextBox control. This helper is just based on the code published by Martin Muller in http://msdn.microsoft.com/en-us/library/ms996492.aspx. It provides an extension method for VS 2008 user so all you have to do is call RichTextBox.Print.

The implementation is simple. The RichTextBoxPrintHelper creates or receives an instance of a PrintDocument object, and event handlers are added to it for the BeginPrint, PrintPage and EndPrint events.

    private int m_nFirstCharOnPage;

    private void printDocument_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        // Start at the beginning of the text
        m_nFirstCharOnPage = 0;
    }

    private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
        // To print the boundaries of the current page margins
        // uncomment the next line:
        //e.Graphics.DrawRectangle(System.Drawing.Pens.Blue, e.MarginBounds);

        // make the RichTextBoxEx calculate and render as much text as will
        // fit on the page and remember the last character printed for the
        // beginning of the next page
        m_nFirstCharOnPage = FormatRange(false,
            e,
            m_nFirstCharOnPage,
            control.TextLength);

        // check if there are more pages to print
        if (m_nFirstCharOnPage < control.TextLength)
            e.HasMorePages = true;
        else
            e.HasMorePages = false;
    }

    private void printDocument_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        // Clean up cached information
        FormatRangeDone();
    }
The FormatRange method is called. This method will use the fill out some structures 
with page information and use the RichTextBox handle to send messages that will render
the control contents to the Printer’s HDC.
 
 /// <summary>
    /// Calculate or render the contents of our RichTextBox for printing
    /// </summary>
    /// <param name="measureOnly">If true, only the calculation is performed,
    /// otherwise the text is rendered as well</param>
    /// <param name="e">The PrintPageEventArgs object from the
    /// PrintPage event</param>
    /// <param name="charFrom">Index of first character to be printed</param>
    /// <param name="charTo">Index of last character to be printed</param>
    /// <returns>(Index of last character that fitted on the
    /// page) + 1</returns>
    public int FormatRange(bool measureOnly, PrintPageEventArgs e,
        int charFrom, int charTo)
    {
        // Specify which characters to print
        STRUCT_CHARRANGE cr;
        cr.cpMin = charFrom;
        cr.cpMax = charTo;

        // Specify the area inside page margins
        STRUCT_RECT rc;
        rc.top = HundredthInchToTwips(e.MarginBounds.Top);
        rc.bottom = HundredthInchToTwips(e.MarginBounds.Bottom);
        rc.left = HundredthInchToTwips(e.MarginBounds.Left);
        rc.right = HundredthInchToTwips(e.MarginBounds.Right);

        // Specify the page area
        STRUCT_RECT rcPage;
        rcPage.top = HundredthInchToTwips(e.PageBounds.Top);
        rcPage.bottom = HundredthInchToTwips(e.PageBounds.Bottom);
        rcPage.left = HundredthInchToTwips(e.PageBounds.Left);
        rcPage.right = HundredthInchToTwips(e.PageBounds.Right);

        // Get device context of output device
        IntPtr hdc = e.Graphics.GetHdc();

        // Fill in the FORMATRANGE struct
        STRUCT_FORMATRANGE fr;
        fr.chrg = cr;
        fr.hdc = hdc;
        fr.hdcTarget = hdc;
        fr.rc = rc;
        fr.rcPage = rcPage;

        // Non-Zero wParam means render, Zero means measure
        Int32 wParam = (measureOnly ? 0 : 1);

        // Allocate memory for the FORMATRANGE struct and
        // copy the contents of our struct to this memory
        IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr));
        Marshal.StructureToPtr(fr, lParam, false);

        // Send the actual Win32 message
        int res = SendMessage(control.Handle, EM_FORMATRANGE, wParam, lParam);

        // Free allocated memory
        Marshal.FreeCoTaskMem(lParam);

        // and release the device context
        e.Graphics.ReleaseHdc(hdc);

        return res;
    }
 

Using the RichTextBox is even more simple. You add a richtextbox to a form and call the Print method:

image

        private void printToolStripMenuItem_Click(object sender, EventArgs e)
        {
            richTextBox1.Print();
        }

I’m attaching the source code for the helper an this sample application so you can use this.

DOWNLOAD SOURCE CODE

DDE in .NET

10. June 2009 04:51 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

Someone recently made me remind an old technology called DDE.

“Dynamic Data Exchange (DDE) is a technology for communication between multiple applications under Microsoft Windows or OS/2

 

“The primary function of DDE is to allow Windows applications to share data. For example, a cell in Microsoft Excel could be linked to a value in another application and when the value changed, it would be automatically updated in the Excel spreadsheet. The data communication was established by a simple, three-segment model. Each program was known to DDE by its "application" name. Each application could further organize information by groups known as "topic" and each topic could serve up individual pieces of data as an "item". For example, if a user wanted to pull a value from Microsoft Excel which was contained in a spreadsheet called "Sheet1" in the cell in the first row and first column, the application would be "Excel", the topic "Sheet1" and the item "r1c1".

Note: In DDE, the application, topic and item are not case-sensitive.”

 

So in VB6 you can have something like:

 

Private Sub Form_Load()
Text1.LinkMode = 0
Text1.LinkTopic = "Excel|Sheet1"
Text1.LinkItem = "R1C1"
Text1.LinkMode = 1
End Sub

 

 

How can you do that in .NET. Is it possible in C#? Well I started looking around and found several forums explaining about all the API calls and I was just about to write my own solution when I found NDDE. This project hosted in CodePlex “provides a convenient and easy way to integrate .NET applications with legacy applications that use Dynamic Data Exchange (DDE)” :)

 

So this is a  nice example of how to do the previous lines in C#:

        //This class provides the infraestructure for DDE comunication
        NDde.Client.DdeClient ddeClient_TextBox1 = null;

        private void Form1_Load(object sender, EventArgs e)
        {
            //I initialize the DDEClient object. Application is Excel and Topic is Sheet1. I'm using the 
            //the TextBox as the syncronization object
            ddeClient_TextBox1 = new NDde.Client.DdeClient("Excel", "Sheet1", textBox1);
            //Connect to the DDE Server
            ddeClient_TextBox1.Connect();
            //Start the Advise Loop
            ddeClient_TextBox1.StartAdvise("R1C1", 1, true, 60000);
            //Setup the Advise Method
            ddeClient_TextBox1.Advise += new EventHandler<NDde.Client.DdeAdviseEventArgs>(ddeClient_TextBox1_Advise);
            //Setup a method to Poke the Server for TextBox cahnges
            textBox1.TextChanged += new EventHandler(textBox1_TextChanged);

        }
        
        void textBox1_TextChanged(object sender, EventArgs e)
        {
            //Syncronous Poking the server
            ddeClient_TextBox1.Poke("R1C1", textBox1.Text + "\0", 4000);
        }

        const string DDE_postFix = "\r\n\0";
        void ddeClient_TextBox1_Advise(object sender, NDde.Client.DdeAdviseEventArgs e)
        {
            //Advise only if needed
            if (e.Text.Length >=DDE_postFix.Length && textBox1.Text + DDE_postFix != e.Text)
                textBox1.Text = e.Text.Substring(0,e.Text.Length-3);
        }
NOTE: Remember that you need to download NDDE and add a reference to this library

This is very good library, you can also set up a lot of Async calls to even improve performance. I have even thought of making an extender as the ToolTip control to add LinkTopic, LinkMode and LinkItem properties for Winforms controls or provide extensions methods to make all the syntax easier, but that is for a future post. Good Luck.

Categories