Tuesday, September 13, 2005
Starting Word Programmatically
When Word starts it locks the default template (normal.dot). When you start a second instance of Word, the second instance will notify you that the file is locked and the first instance will tell you that the default template has been changed by another Word session. When you start the Word application it will automatically reuse an existing instance but if you start an instance programmatically you have to do that yourself.
/// <summary>
/// This function will return a word application object,
/// either the one that is already running or a new one.
/// </summary>
/// <param name="PbReusedExistingWordApplication">
/// True if an existing word application is returned
/// </param>
/// <returns>
/// The Word application
/// </returns>
private static Word.ApplicationClass GetOrStartWordApplication(
out bool PbReusedExistingWordApplication)
{
Word.ApplicationClass LobjWordApplication = null;
// Check if the Word process is running
if (Process.GetProcessesByName("WINWORD").Length != 0)
{
// Get a pointer to the Word application
object LobjWordApplicationAsObject =
Marshal.GetActiveObject("Word.Application");
if (LobjWordApplicationAsObject == null)
{
throw new ApplicationException(
"Unable to attach to the running Microsoft Word");
}
LobjWordApplication =
LobjWordApplicationAsObject as Word.ApplicationClass;
PbReusedExistingWordApplication = true;
}
else
{
// Create a new instance of the word application
LobjWordApplication = new Word.ApplicationClass();
PbReusedExistingWordApplication = false;
}
return LobjWordApplication;
}
The function can either be used directly or you can wrap it in a utility class with an Open and Close function which stores the reused Boolean internally and depending on it either closes Word or leave it open.
/// <summary>
/// This function will return a word application object,
/// either the one that is already running or a new one.
/// </summary>
/// <param name="PbReusedExistingWordApplication">
/// True if an existing word application is returned
/// </param>
/// <returns>
/// The Word application
/// </returns>
private static Word.ApplicationClass GetOrStartWordApplication(
out bool PbReusedExistingWordApplication)
{
Word.ApplicationClass LobjWordApplication = null;
// Check if the Word process is running
if (Process.GetProcessesByName("WINWORD").Length != 0)
{
// Get a pointer to the Word application
object LobjWordApplicationAsObject =
Marshal.GetActiveObject("Word.Application");
if (LobjWordApplicationAsObject == null)
{
throw new ApplicationException(
"Unable to attach to the running Microsoft Word");
}
LobjWordApplication =
LobjWordApplicationAsObject as Word.ApplicationClass;
PbReusedExistingWordApplication = true;
}
else
{
// Create a new instance of the word application
LobjWordApplication = new Word.ApplicationClass();
PbReusedExistingWordApplication = false;
}
return LobjWordApplication;
}
The function can either be used directly or you can wrap it in a utility class with an Open and Close function which stores the reused Boolean internally and depending on it either closes Word or leave it open.
Tuesday, August 30, 2005
Dynamically Generating Images in ASP .NET
In .NET it is very easy to output a raw image from a web page instead of the default HTML. All you have to do is change the content type and stream the image as binary data to the response:
protected void Page_Load(object sender, EventArgs e)
{
Bitmap bitmap = new Bitmap(320, 240);
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Png);
Response.Clear();
Response.ClearContent();
Response.ContentType = "image/png";
Response.BinaryWrite(memoryStream.ToArray());
Response.End();
}
The above code will stream an empty image in the PNG format to the caller of this page. The image can either be rendered dynamically or read from a database, the sky is the limit. By using request parameters the image rendering can easily be customized.
The resulting image page can be embedded on other pages on the web site, either statically by referencing it in a HTML image element or dynamically by using the ASP .NET image server control. In the latter case you just assign the URL of the image page to the ImageUrl property of the control.
protected void Page_Load(object sender, EventArgs e)
{
Bitmap bitmap = new Bitmap(320, 240);
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Png);
Response.Clear();
Response.ClearContent();
Response.ContentType = "image/png";
Response.BinaryWrite(memoryStream.ToArray());
Response.End();
}
The above code will stream an empty image in the PNG format to the caller of this page. The image can either be rendered dynamically or read from a database, the sky is the limit. By using request parameters the image rendering can easily be customized.
The resulting image page can be embedded on other pages on the web site, either statically by referencing it in a HTML image element or dynamically by using the ASP .NET image server control. In the latter case you just assign the URL of the image page to the ImageUrl property of the control.
Monday, August 29, 2005
DataGridView Rich Text Column
A lot of applications need to display formatted text in a table. Out of the box the DataGridView only supports a TextBox column type for which the formatting options are quite limited. As the cells within a DataGridView are no standard user controls it is no trivial task to implement. I implemented a RichTextBox column type based on the following Microsoft knowledge base article:
Print the Content of a RichTextBox
This article shows how to render the RichTextBox content on a printer device context using the EM_FORMATRANGE message. Instead of the printer I let this code render the content on a device context for a Bitmap:
Image LimgImage = ...
Graphics LobjGraphics = Graphics.FromImage(LimgImage);
IntPtr LobjHdc = LobjGraphics.GetHdc();
This image is then rendered in the Paint event of the cell.
In my application it was furthermore necessary to edit the content in the cells. For this I used the default RichTextBox. It required quite some time to get the sizing right so the image rendered in display mode exactly fitted the content of the RichTextBox used to enter the content, but at the end all the magic numbers involved were :-)
Print the Content of a RichTextBox
This article shows how to render the RichTextBox content on a printer device context using the EM_FORMATRANGE message. Instead of the printer I let this code render the content on a device context for a Bitmap:
Image LimgImage = ...
Graphics LobjGraphics = Graphics.FromImage(LimgImage);
IntPtr LobjHdc = LobjGraphics.GetHdc();
This image is then rendered in the Paint event of the cell.
In my application it was furthermore necessary to edit the content in the cells. For this I used the default RichTextBox. It required quite some time to get the sizing right so the image rendered in display mode exactly fitted the content of the RichTextBox used to enter the content, but at the end all the magic numbers involved were :-)
Tuesday, August 23, 2005
Searching Generic Collections
When you find something it is always in the last place you were searching for it. Searching through generic collections using anonymous delegates however has never been as much fun. Consider a list of type Person, now take a look at the following line of code:
List<Person> youngPersons = persons.FindAll(YoungerThan(18));
Now this is what I call self explaining code without being too specific.
The YoungerThan function returns a predicate that will test an item in the collection and returns a Boolean value indicating the truth of the predicate. It is very cool that the predicate delegate can be parameterized, for instance in the YoungerThan implementation:
private Predicate<Person> YoungerThan(int age)
{
Predicate<Person> youngerThan = delegate(Person person)
{
return person.Age < age;
};
return youngerThan;
}
The function can either be implemented in the class in which you use it or as part of the class to be searched. In the latter case you would probably implement it as a static function.
List<Person> youngPersons = persons.FindAll(YoungerThan(18));
Now this is what I call self explaining code without being too specific.
The YoungerThan function returns a predicate that will test an item in the collection and returns a Boolean value indicating the truth of the predicate. It is very cool that the predicate delegate can be parameterized, for instance in the YoungerThan implementation:
private Predicate<Person> YoungerThan(int age)
{
Predicate<Person> youngerThan = delegate(Person person)
{
return person.Age < age;
};
return youngerThan;
}
The function can either be implemented in the class in which you use it or as part of the class to be searched. In the latter case you would probably implement it as a static function.
Monday, August 22, 2005
Autoscrolling Panel Bug
The current implementation of the Panel control contains a bug in the autoscrolling code. Whenever the panel control gets the focus it will scroll the active subcontrol in to view. This bug is acknowledged by Microsoft but, because it is “previously shipped behavior”, it will not be resolved in the next Whidbey release :-(
See also: Bug Details: AutoScroll positon gets losted
Overriding this behavior was not easy as a lot of methods involved in the autoscrolling are not virtual. After some hacking we found a “workaround” for the bug by always scrolling to the current position, which effectively does nothing.
public class MyPanel : Panel
{
protected override Point ScrollToControl(Control PobjActiveControl)
{
return AutoScrollPosition;
}
}
This however not only disables the bug behavior but almost all the autoscrolling behavior too. Therefore the desired autoscrolling behavior, for instance when the user tabs through the panel’s subcontrols, has to be coded manually. Luckily for my project this was already in place althought it only requires a few lines of code to accomplish.
See also: Bug Details: AutoScroll positon gets losted
Overriding this behavior was not easy as a lot of methods involved in the autoscrolling are not virtual. After some hacking we found a “workaround” for the bug by always scrolling to the current position, which effectively does nothing.
public class MyPanel : Panel
{
protected override Point ScrollToControl(Control PobjActiveControl)
{
return AutoScrollPosition;
}
}
This however not only disables the bug behavior but almost all the autoscrolling behavior too. Therefore the desired autoscrolling behavior, for instance when the user tabs through the panel’s subcontrols, has to be coded manually. Luckily for my project this was already in place althought it only requires a few lines of code to accomplish.
Friday, August 19, 2005
Recustomize Word Document
Removing the customization from a Word document not only removes the application manifest but also the cached data. Althought it is well documented by Microsoft it still bit me.
http://msdn2.microsoft.com/library/ms185679(en-us,vs.80).aspx
For a solution it was necessary to change the reference to the customization assembly within the document. I did not want to store any data in the original document as it might contain sensitive information. I therefore created a copy of the document in the temporary folder of the current user. As I did not want to copy the customization assembly to the temporary folder and also did not want to hardcode the application path the only option left was to "recustomize" the document to reference the customization assembly in the original application folder.
/// <summary>
/// This function will recustomize the given word document
/// with the given customization assembly
/// </summary>
/// <param name="PstrDocumentFileName">
/// The name of the Word document
/// </param>
/// <param name="PstrCustomizationAssemblyFileName">
/// The customization assembly
/// </param>
private static void RecustomizeDocument(
string PstrDocumentFileName,
string PstrCustomizationAssemblyFileName)
{
// Remove the old customization
if (ServerDocument.IsCustomized(PstrDocumentFileName))
{
ServerDocument.RemoveCustomization(PstrDocumentFileName);
}
// Add the new customization
Assembly LobjCustomizationAssembly =
Assembly.LoadFile(PstrCustomizationAssemblyFileName);
string LstrCustomizationAssemblyVersion =
LobjCustomizationAssembly.GetName().Version.ToString();
bool LcbMakePathsRelative = false;
const string LcstrDeploymentManifestPath = "";
ServerDocument.AddCustomization(
PstrDocumentFileName,
PstrCustomizationAssemblyFileName,
LcstrDeploymentManifestPath,
LstrCustomizationAssemblyVersion,
LcbMakePathsRelative);
}
http://msdn2.microsoft.com/library/ms185679(en-us,vs.80).aspx
For a solution it was necessary to change the reference to the customization assembly within the document. I did not want to store any data in the original document as it might contain sensitive information. I therefore created a copy of the document in the temporary folder of the current user. As I did not want to copy the customization assembly to the temporary folder and also did not want to hardcode the application path the only option left was to "recustomize" the document to reference the customization assembly in the original application folder.
/// <summary>
/// This function will recustomize the given word document
/// with the given customization assembly
/// </summary>
/// <param name="PstrDocumentFileName">
/// The name of the Word document
/// </param>
/// <param name="PstrCustomizationAssemblyFileName">
/// The customization assembly
/// </param>
private static void RecustomizeDocument(
string PstrDocumentFileName,
string PstrCustomizationAssemblyFileName)
{
// Remove the old customization
if (ServerDocument.IsCustomized(PstrDocumentFileName))
{
ServerDocument.RemoveCustomization(PstrDocumentFileName);
}
// Add the new customization
Assembly LobjCustomizationAssembly =
Assembly.LoadFile(PstrCustomizationAssemblyFileName);
string LstrCustomizationAssemblyVersion =
LobjCustomizationAssembly.GetName().Version.ToString();
bool LcbMakePathsRelative = false;
const string LcstrDeploymentManifestPath = "";
ServerDocument.AddCustomization(
PstrDocumentFileName,
PstrCustomizationAssemblyFileName,
LcstrDeploymentManifestPath,
LstrCustomizationAssemblyVersion,
LcbMakePathsRelative);
}