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.
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
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
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
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 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));
}
}
}
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
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;
}
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.
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