Crystal Reports was a tool very used by VB6 developers.
Sometimes when you are migrating a VB6 applications, you find a lot of reports created with this tool.
What should you do? Should you rewrite them on another technology?
Well that really depends on project requirements, but Crystal Reports has good support for VS2010, both for 32 and 64 bit environments.
So you don't need to throw away your reports you can still use Crystal Reports.
However that hard thing to do is where to find Crystal Reports for VS. Do you need to buy some software for that?
I have found that you can download Crystal Reports for VS2010 is just that the links are a little hidden. My friend Victor send me the following information which has been very useful for this task:
a) For Deployment Environment:
1. Download the Redistributable Package for Visual Studio according to the platform (32 bits or 64 bits). And install it. You will find the installer in the following link:
2. Give access to the user executing the IIS process to the path for the Temp Folder (C:\Windows\Temp or the corresponding folder on windows installation).
3. Download the "Microsoft SQL Server 2008 Feature Pack", where you can find the "Microsoft SQL Server 2008 Native Client ". Download the corresponding installer according to the platform of the server (x86, x64 or IA64).
b) For Development environment:
1. Download the "Complete Package" for development on Visual Studio 2010. Please be sure that if you're on a 64 bits machine, you check the "Install 64 bits Runtime" before you click on finish when you complete the installation.
2. Be sure that the current user running on your machine has access to the Temp Folder (C:\Windows\Temp or the corresponding folder on windows installation)
3. If you don't have the Microsoft SQL Server 2008 Native Client Provider (sqlncli10), download the "Microsoft SQL Server 2008 Feature Pack", where you can find the "Microsoft SQL Server 2008 Native Client ". Download the corresponding installer according to the platform of the server (x86, x64 or IA64).
4. To create a new report please be sure you use a "OLE DB (ADO)" Connection and that you're using the "SQL Server Native Client 10.0" Provider, and use the right login information. If the report was previously created with a different provider, please change it on the "Set Datasource Location", editing the Provider field.
This is a very strange error that you can find sometimes when working with ADO.NET.
David McKean from MSFT says:
This occurs when you have multiple DataReaders open concurrently on the same connection,
ie you call SqlCommand.ExecuteReader but don't close the SqlDataReader returned by this
method before calling it again (either on the same command or another command on the same connection).
It requires a feature called MultipleActiveResultSets which is not available in all providers.
For example SQL2000 does not support it, it was implemented starting from SQL2005.
Also .NET 2.0 must be used.
For more information about enabling Multiple Active Result Sets see: http://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.80).aspx
A good recommendation to make sure that the the readers are closed is to put them inside a using statement, in that case,
no matter if an exception happened they will be closed and disposed.
If you are using SQL Server 2000, MARS is not available so you can create two different connection objects.
Another good article about this issue is: http://blogs.msdn.com/b/spike/archive/2009/08/20/there-is-already-an-open-datareader-associated-with-this-command-which-must-be-closed-first-explained.aspx
But in general to use it is just a change in the connection string:
<connectionStrings>
<clear />
<add name="VasquezDB"
connectionString="Data Source=rvasquez;Initial Catalog=VasquezDB;
Integrated Security=True;MultipleActiveResultSets=Yes" />
</connectionStrings>
Good Luck
NOTE: a good link with more details about MARS is:
http://blog.typps.com/2011/04/mars-multiple-active-result-sets.html
As I use to start stories to my kids when they go to bed:
“A long long time ago when I was a kid there existed a tool called
.NET Reflector, by a great wizard called Lutz Roeder. This wizard created
a magical tool that allowed you to discover what where the dark forces of the
Framework that produced weird behavior. And he provided it to the code warrior so
they could find their path”
Well .NET Reflector still exists and is a great tool, but is no longer free. You can no longer
just download it and enjoy its bliss : S
But is everything loss. Should I sell my sword and horse to be able to use the .NET reflector Oracle jQuery15203662828153464943_1359277348820?
No. There are now good free alternatives.
First one is call ILSpy(http://wiki.sharpdevelop.net/ILSpy.ashx). It looks almost the same as .NET reflector. Is not the same but is very good
And recently JetBrains a clan of powerful code warlocks, has just release another magical jewel,
they called it dotPeek (http://www.jetbrains.com/decompiler/):
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;
}
For VB6 applications it is common to rely on OS or Kernel API Calls. Some of those APIs might
need you to send data back and for the native API.
Marshalling in .NET can be complicated and bothersome. I have published several posts about
interop. But it usually depends on adding several marshalling attributes and even tricks specially for
fixed strings.
So I decided to provide a more a simpler approach for conversion. In this approach you just need to things:
1. Your VB6 types or structs will be mapped to .NET classes
2. All VB6 type or struct fields will be mapped to public fields
3. An attribute must be used on those fields to indicate the field length, for arrays or strings.
4. Extension methods .AsString() .SetFromString and .GetClassLength will handle all the complexities of setting the struct fields.
Let’s see an example:
Type EmployeeRecord
FirstName As String * 5
LastName As String * 5
End Type
That vb6 type will be mapped in .NET following this approach to:
public class EmployeeRecord
{
[FixedLength(5)]
public string FirstName = "Mau";
[FixedLength(5)]
public string LastName = "Rojas";
}
You can then simple use that class in .NET
var emp = new EmployeeRecord {FirstName="Mauricio",LastName="Rojas"} ;
var str = emp.AsString();
//This str value will be "MauriRojas" the helper extension methods
// .AsString and .SetFromString will handle setting the internal class fields
All that is very good but how is this used in Marshalling?? Well very simple. Let’s say you have a Dll called foo.dll
with a function foo that receives an EmployeeRecord:
[DllImport("foo.dll")]
public static extern int foo(IntPtr Struct);
Then if you want to call that function you will do something like:
var emp = new EmployeeRecord { FirstName="Ann",LastName="Smith"};
string str = emp.AsString();
var ptr = IntPtr.Zero;
ptr = Marshal.StringToBSTR(str);
//or
ptr = Marshal.StringToHGlobalAnsi(str);
//or
ptr = Marshal.StringToHGlobalAuto(str);
//or
ptr = Marshal.StringToHGlobalUni(str);
//And call the native function
foo(ptr);
If the function modifies the structure and you want to reflect those changes then you will do something like:
str = Marshal.PtrToStringAnsi(ptr,typeof(EmployeeRecord).GetClassLength())
emp.SetFromString(str);
This solution can also be applied for more complex structures. For example:
public class EmployeeRecord
{
[FixedLength(5)]
public string FirstName = "Mau";
[FixedLength(5)]
public string LastName = "Rojas";
}
public class Record1
{
public int field1;
[FixedLength(10)]
public string field2 = "";
public EmployeeRecord rec = new EmployeeRecord();
}
public class GeneralInfo
{
public int field1;
[ArrayLength(5)]
[FixedLength(2)]
public String[] countrycodes = { "cr","es","mx","pa","ni"};
[FixedLength(2)]
public EmployeeRecord[] employees;
}
If you want to try it out this is the link to the CODE
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
Windows Azure is a great platform and the escalatity oportunities are great,
and deployment time is also great.
You can have all your website up and running in just 10-15minutes.
But… and yes there is always a but.
Sometimes you can have a WebSite that is not that static, that as a matter of fact
you are changing its views constantly. Specially if some ideas are not finished.
And yes you can test locally, but there is also a situation where you might want to have that flexibility.
Well looking around I found a very interesting solution by
Maatern Balliauw. http://blog.maartenballiauw.be/post/2009/06/09/A-view-from-the-cloud-(or-locate-your-ASPNET-MVC-views-on-Windows-Azure-Blob-Storage).aspx
What he proposes is to use windows azure storage as a virtual file system, so you can with simple tools
like the Windows Azure Explorer modify your web pages without the need of going through a lengthy republish process.
So go ahead and keep enyoing Azure
While solving a bug with a custom class that extended the System.Data.DataSet class, I found a situation where the class implemented, the ISerializable interface, but for some reason, during the call to the base.GetObjectData in my serialization code it was trying to get the value of some properties that caused an exception.
The reason was that those properties were not “ready” because my serialization code had not finish initializing the object. But why was the Dataset.GetObjectData trying to get or set those values.
It seems that there is some code in the dataset that used reflection to get the object properties and try to serialize them. I did not want that.
How could I stop the framework from doing that?
I thought of the NonSerializable attribute but that works only on fields and what I have is a property.
I thought of the XmlIgnore attribute but it had no effect.
Why!!!!
Well I finally found that you can add a couple of (not attributes) methods to your component.
They should be named Reset<Property>() and ShouldSerialize<Property>() and returning a boolean value
from these functions will control if the properties are serialized or not.
For more info see MSDN page for ShouldSerialize
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
}
}