Tuesday, April 19, 2011

Using Crystal Reports Basic for VS2008 with VS2005

I had a client for whom I developed a project in Visual Studio 2005. The reports were written for the bundled version 10.2 of Crystal Reports (CR). The application was to be installed on the client's server that already had a number of applications that were using the later Crystal Reports version 10.5 (bundled as Crystal Reports Basic for Visual Studio 2008), a newer version.

Anecdotal evidence showed it was possible to use a version of CR other than what was bundled with VS2005. Unfortunately, no hard documentation existed that defined or instructed one on how to do so. These notes are what I pieced together after a week of intense research and trial & error.

The first thing that must be done is to create and test reports with CR v10.2 that is bundled with VS2005. Easy, right? Of course I'm being sarcastic.

The next step is to install the CR v10.5 runtime package. These can be acquired from several places, the official one being the Crystal Reports page on the SAP Community Network web site. Another source is Visual Studio 2008 Pro, if you have it handy. The x86 package, though, is hidden deep in the bowels of the installation: C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\CrystalReports10_5\CRRedist2008_x86.msi. Install it on your machine; I've encountered no problems with it co-existing with CR v10.2.

Depending on your architecture, you need one of these setup packages:
   CRRedist2008_ia64.msi (64 bit Itanium)
   CRRedist2008_x64.msi (64 bit Intel)
   CRRedist2008_x86.msi (BootStrapper)

In your project, make a list of all references to the CR assemblies and remove them. Here comes the fun part. You can't simply add references to the new assemblies. From a command prompt, go to your %windir%\assembly directory. You will find GAC, GAC_32, and GAC_MSIL directories there. Inside those are subdirectories for each version of the Crystal Reports assemblies on your machine. Copy the 10.5.* version of the DLLs to a folder you create within your project: I called mine CRRedist2008.

In your project, add references by browsing to your CRRedist2008 folder and selecting the same assemblies as were in your original list. Why the copying? Visual Studio does not allow adding references to assemblies in the General Assembly Cache. You need separate files so VS lets you add them as references to your project.

One more step: if you check the reference properties, you may still see the v10.2 assembly being referenced. Set the Use Specific Version property to True to force VS to use the v10.5 assembly you added as a reference.

Recompile and your reports should now run with the VS2008 version of the Crystal Reports libraries.

Barrel of fun, huh?

Thursday, March 10, 2011

Updating the MDI parent menu icon for maximized child form

While working on a C# .NET program recently, I ran up against an infamous (and another apparently long-standing) bug in Microsoft's Multiple Document Interface (MDI) architecture. When a child form has been maximized, and its window icon is updated, the icon that is merged into the MDI parent's menu strip is not updated to reflect the change.

I was using the child window icon to identify the document type being displayed and to mark when it had been modified. Everything worked fine when the window was open in Normal mode, but the icon stopped being updated when the child was maximized to fill its parent's client area.

After literally days of searching the web to see if anybody else solved the problem, I found answers ranging from "impossible" to "keep track of your own windows" (a.k.a. the old roll-your-own interface standby). Digging through the layers of classes with the debugger, I figured out a hack that seems to work.

Here's the basis of my solution: when a child window is maximized, the controls on its title bar are merged with its parent window. The parent menu inserts an image as its first element that is a copy of the child window's icon. The image (a Bitmap image) is copied only at the time the child is maximized, and is never updated afterward, regardless of calling Refresh(), Update(), Invalidate(), etc. So, to get around this missing functionality, my solution was to check the parent menu when I changed the icon of the child window; if it had an image in its first element, I converted the Icon into a Bitmap and replaced the image. It may sound hokey, but it worked.

These are the steps I used for updating the icon. I raised an event to update the parent form.


// Change the child icon according to the document state.
// Called from a method in the child form.
int newState = this.GetChildState();
this.Icon = (newState == 0) ?
myProject.Properties.Resources.savedicon :
myProject.Properties.Resources.modifiedicon;

// ...raise event to notify the parent. sender is the child form.

// Change the parent menu icon when child is maximized.
// Called from an event handler in the parent form.
if (MainMenuStrip.Items[0].Image != null)
{
// Copy the new state icon from the child window.
MainMenuStrip.Items[0].Image = ((Form)sender).Icon.ToBitmap();
}


The MDI architecture has its quirks. This is just a way to get around one of them. I think this is an inelegant solution to an inelegant problem, but sometimes you just have to be practical.

Saturday, February 12, 2011

RichTextBox.SelectedRtf property has incomplete documentation

The Microsoft documentation for this property is missing critical information (there's a shock, huh?). It explains use of the property in terms of text. It's not text that is being used here, it is a completely formatted document. That document is extracted from or integrated into the complete document stored within the RichTextBox control.

If used to insert text, the assigned string must be a correctly-formatted RTF document (even a minimal document will work) or it will throw a System.ArgumentException error, with the unhelpful inner message of "File format is not valid", especially maddening when not working with files.

To illustrate an incorrect and correct use of this property, in the example code snippets below, myRtfControl is an instance of the RtfTextBox control.

Example that fails:

myRtfControl.Select(myRtfControl.TextLength, 0); // Set caret to append

text.myRtfControl.SelectedRtf = "\ul underlined text\ul0";

// ^ Throws System.ArgumentException


Example that works:

myRtfControl.Select(myRtfControl.TextLength, 0); // Set caret to append

text.myRtfControl.SelectedRtf = "{\rtf1\ul underlined text\ul0}";

// ^ Adds underlined text as expected.


Note that for an empty selection, SelectedRtf actually contains the following document:

{\rtf1\ansi\ansicpg1252\deff0\deflang1033\uc1 }


The text consists of a single Unicode space character "\uc1 ". It's this "empty" document that assignment will replace.

Gathering this information came at the expense of several days of wasted time poring through tangential examples on the web (and some peeled wall paint from cursing bad documentation). Thanks to those intrepid pioneers.

For further reference, a link to the Rich Text Format Specification, version 1.6 can be found at http://msdn.microsoft.com/en-us/library/aa140277%28office.10%29.aspx.

Happy document bug hunting!

Thursday, January 20, 2011

MobileLPR - LPR client for.NET Compact Framework 2.0

I recently wrote a client library for Windows CE/Mobile devices, giving them the ability to print to a network printer. The library and article, MobileLPR - LPR client for.NET Compact Framework 2.0 - CodeProject, are usable under The Code Project Open License (CPOL).

The library allows printing to network print servers implementing the LPR, LPRng, and SocketAPI (direct socket connection) printing protocols. LPR and SocketAPI are venerable and widely-used on all major platforms. Since most contemporary printers offer built-in network printing using one of these protocols, this library is a great addition to your mobile developer's tool belt.

I've been going to the Code Project web site for years. It is a nice place to learn and contribute!

Monday, November 15, 2010

Differences between enum in C# and C/C++


Recently I encountered a confusing situation that took me by surprise while porting some old, well-proven C++ code to C#: the languages treat enumerated types differently. In C and C++, an enum defines a value type that represents a restricted set of values. An enum type can be declared just about anywhere before it is used, including statement blocks.

In C#,  an enum defines a class (i.e. types are classes), meaning it can be declared only at the namespace or class level; it cannot be defined in a statement block within a method as it can in C++. If you try, Visual Studio issues an error not about the enum definition itself, but instead at the opening brace of the method, stating '} expected' rather than something useful like 'enum cannot be defined at statement level' or even 'invalid statement'. Another unintuitive error message brought to you courtesy of Microsoft.

So if you're a C++ programmer learning C#, beware this trap. It's easily fixed by moving (and naming or renaming) your enumeration to a less narrowly scoped portion of your module, but it's annoying and unwelcome, nonetheless.

Monday, September 20, 2010

Mobile Device Emulator Error on Windows 7: No VPC network adapters enumerated

After upgrading from Windows Vista (pronounced fiasco) to Windows 7 (OMG, there is life after XP!) and reloading everything because there was no migration path (probably for the best anyway), the Windows Mobile device emulator I was using could not be configured with a network connection.

Several articles were well-written and helped me to understand that it could have been something other than missing the Virtual Machine Network Services driver that was the source of the error (which was not my problem). Brian Peek's Windows Virtual PC and the Microsoft Device Emulator article was a excellent piece that led me to try other debugging perspectives. Ben Armstrong's (Virtual PC Guy's Blog) Fixing broken virtual networking was concise and gave me other clues about dealing with the virtual network driver. Many thanks to them for sharing their experiences.

I tried both solutions and neither fixed the problem. What finally worked was getting to the emulator's connection settings through Visual Studio, since the options I needed were disabled within the emulator itself. Go to Tools > Options > Device Tools > Devices, pick a preferred emulator, click the Properties... button, then the Emulator Options button there. On the Network tab, I added a check to the Enable NE2000 PCMCIA network adapter and bind to: checkbox. The critical step was selecting the actual connected network card - not the Connected Network Card option - in the network adapters dropdown box.

Next time I started the emulator, it saw the network adapter and was able to use it. Without selecting the actual network card, the emulator must try to use a hidden, disconnected network adapter (I have both Ethernet and WiFi network adapters in my notebook, both with virtual network services enabled) and fails to see the connected one, hence the notorious error:


No VPC network adapters enumerated or no host network adapter with provided MAC address found


I tried some other solutions, such as replacing the virtual machine network services driver with the one from Virtual PC 2007. It didn't work either, so I uninstalled and reinstalled the VMNS driver that came with Windows 7's Virtual PC. I also cleared the emulator's saved state several times during my ordeal.

Obviously this workaround to the bug in the VM's network device driver has the drawback that you have to reconfigure your network connection if you change network adapters. But at least it got me working again.

For reference, the development platform I got this working on is Windows 7 32-bit Tablet PC with 2 GB RAM, 100GB hard drive, 1.7 GHz CPU and Windows XP Mode with Vista/7 Service Pack. I use Visual Studio 2005 SP2 with the Window Mobile 5 Pocket PC emulator from the Microsoft Windows Mobile 5 SDK. I do a lot of industrial handheld computer development.

Thursday, June 10, 2010

Error: ShowDialog exits immediately

I was trying to implement a WinForm login class called from a menu navigation class, but when I called ShowDialog(), it was exiting immediately without displaying the window or setting the DialogResult code. It was working the first time through the loop, but subsequent calls always failed. I searched all over the 'net for answers without success; in the end, I had to trace through my program a few lines at a time in the debugger until I found the culprit.

Watching the behavior of the preparation call to Login.ShowDialog() resulted in everything appearing normal the first time. Later, the Owner property would not allow assignment - no error, but the value stayed null - and calling ShowDialog() would return immediately with the default value of DialogResult.None. My Load event was even being called as expected. To me, this appeared to be working incorrectly. It turned out to be not incorrect, but undefined behavior I was seeing.

As the stereotypical engineer, I nearly outsmarted myself by designing a library of classes that would handle the details of performing their appropriate menu operations and allow me to define the menus declaratively. One of the classes was responsible for calling a named method and instantiating its type object through reflection if needed. To allow a ShowDialog() method call, the .NET Framework requires the object to be manually disposed afterward. I got fancy and added code to call Dispose() on the object automatically if it was a Form. But...some of my called methods were part of my active menu navigation class, which was itself a Form. When the menu operation was executed, my navigation class got destroyed, so I was clobbering myself.

The resolution was to be careful to not dispose of the class I was actively using. Assigning a disposed Form as the owner of another Form is undefined, pointless, and creates bizarre results, as I so painfully found. Fixing this issue solved the inconsistent behavior. I'm deservedly embarrassed by my mistake, but wiser for it.

If this saves somebody else the inconvenience I experienced to track down this problem, then it will have been worth my time to share this story. Leave me a comment if this helped you in any way.