Yesterday I spent a while tracking down a bug in a library that I wrote a while back. One of the things that the library is used for is drawing text in fonts that aren't web safe to be used as headings and whatnot. So if I want the phrase "Out Of Office" drawn in whatever font I just created (this isn't actually a real use case) I can just upload the ttf file to my fonts directory on the server and use the control to generate an image dynamically that will fit the space. The tool even provides caching so after a string with a given set of params has been requested N times it caches it and will load the image from the cache upon future requests. But enough about that.
The bug I tracked down yesterday was related to the ampersand character and the fact that for some reason when the string I wanted to draw contained the ampersand character then no text was being drawn. I wouldn't get any exceptions or anything I just wouldn't get any text. The result image was just the background color I'd requested the size that the text would require if it was in the image. Without the ampersand char it worked as expected.
In GDI when you go to draw a string via Graphics.DrawString you can optionally display hot keys (meaning underlining a specific key to specify that it's a shortcut visually). When I'm drawing a string I'm passing in a StringFormat object which contains some formatting information (horizontal alignment and such). One of the properties of StringFormat is HotkeyPrefix which is used to set the Hotkey functionality for the string (Hide, Show, or None). Now you might think that simply setting it to None (which is the default btw) would result in no Hotkey formatting being considered when rendering the string. But you'd be wrong.
After a great deal of trial and error I got the ampersand to render when I replaced each ampersand with 2 ampersands (an escape of sorts) and then set HotkeyPrefix to Hide. Why this worked I'm not sure and it's kind of counterintuitive but whatever. That's how I fixed it.
Showing posts with label Development. Show all posts
Showing posts with label Development. Show all posts
Friday, January 11, 2008
Thursday, December 13, 2007
I'm still so SO super busy. That said there's some sql stuff I wanted to post.
Copying One Table To Another
Want to copy all the data including the structure (with the exceptions of the constraints) to a new database?
Want to just copy the structure without the rows?
What could be easier.
Unions
Unions are pretty cool. That said if you're using text, ntext, or image types you need to use a Union All. If you don't you'll get an ugly error like this:
Stored Procedure Output Parameters
If you're running a stored procedure that returns a data set you'll need to call SqlCommand.ExecuteReader(). If you also have an output parameter specified in the procedure you might be tempted to do something like this:
The above code will compile fine but you'll get strange exceptions on the line where you're reading out the param value. If you investigate you'll find that even though command.Parameters["foo"] isn't null command.Parameters["foo"].Value is. That's because when you're using a sql reader you need to close the reader before you can read the output param(s):
Now you know. And knowing is half the battle.
Copying One Table To Another
Want to copy all the data including the structure (with the exceptions of the constraints) to a new database?
Insert Into NewDatabaseName
Select * From OldDatabaseName
Want to just copy the structure without the rows?
Insert Into NewDatabaseName
Select * From OldDatabaseName
Where 1 = 2
What could be easier.
Unions
Unions are pretty cool. That said if you're using text, ntext, or image types you need to use a Union All. If you don't you'll get an ugly error like this:
Server: Msg 8163, Level 16, State 4, Line 1
The text, ntext, or image data type cannot be selected as DISTINCT.
Stored Procedure Output Parameters
If you're running a stored procedure that returns a data set you'll need to call SqlCommand.ExecuteReader(). If you also have an output parameter specified in the procedure you might be tempted to do something like this:
sqlDataReader = command.ExecuteReader();
string outputParam = command.Parameters["foo"].Value;
while (reader.Read()) {
//read your rows
}
reader.Close();
command.Connection.Close();
The above code will compile fine but you'll get strange exceptions on the line where you're reading out the param value. If you investigate you'll find that even though command.Parameters["foo"] isn't null command.Parameters["foo"].Value is. That's because when you're using a sql reader you need to close the reader before you can read the output param(s):
sqlDataReader = command.ExecuteReader();
while (reader.Read()) {
//read your rows
}
reader.Close();
string outputParam = command.Parameters["foo"].Value;
command.Connection.Close();
Now you know. And knowing is half the battle.
Monday, October 22, 2007
On Prefix and Postfix Increment Operators
For those familiar with increment operators and their prefix and postfix usage skip down to just below the pseudo-code.
When you have a number and you want to increment it while stepping through a collection or something you can use the increment operator ("++"). That will add one to whatever number you pair with the operator. When using increment operators there are prefix and postfix forms. The prefix form is when you put the operator before the variable containing the number and the postfix is afterwards (++i vs. i++). Functionally these the placement results in very different functionality, if you use the prefix form then the number is incremented before it is evaluated whereas if you use postfix then it is evaluated after it's value is used. In other words (this is really simplified pseudo-code):
Will print the number 00.
Will print 11.
Will print 01.
Awesome, let's continue.
The issue I have with this is that I can only use one at a time. I just came across a scenario where I actually got kind of excited about a line of code I had just written (which doesn't happen very often). The scenario was that I had a string containing tokens I needed however each token I needed was surrounded on both sides by tokens that I don't need. So I had something like:
;junk;ValueICareAbout;junk;junk;ValueICareAbout;junk;
This led me to writing the following lines of code:
Sadly the compiler informed me that what I had done is totally not allowed. Which made me sad. Because ++i++ looks awesome. And yes, I do realize that I'm a huge dork.
When you have a number and you want to increment it while stepping through a collection or something you can use the increment operator ("++"). That will add one to whatever number you pair with the operator. When using increment operators there are prefix and postfix forms. The prefix form is when you put the operator before the variable containing the number and the postfix is afterwards (++i vs. i++). Functionally these the placement results in very different functionality, if you use the prefix form then the number is incremented before it is evaluated whereas if you use postfix then it is evaluated after it's value is used. In other words (this is really simplified pseudo-code):
variable n = 0;
print n;
print n;
Will print the number 00.
variable n = 0;
print ++n;
print n;
Will print 11.
variable n = 0;
print n++;
print n;
Will print 01.
Awesome, let's continue.
The issue I have with this is that I can only use one at a time. I just came across a scenario where I actually got kind of excited about a line of code I had just written (which doesn't happen very often). The scenario was that I had a string containing tokens I needed however each token I needed was surrounded on both sides by tokens that I don't need. So I had something like:
;junk;ValueICareAbout;junk;junk;ValueICareAbout;junk;
This led me to writing the following lines of code:
for (int i = 0; i < tokens.Length; i++) {
string token = tokens[++i++];
Trace("Found Token: " + token);
}Sadly the compiler informed me that what I had done is totally not allowed. Which made me sad. Because ++i++ looks awesome. And yes, I do realize that I'm a huge dork.
Saturday, September 22, 2007
The Excel Interop
I just finished up a project working on a win32 client application that is used to import data from an Excel spreadsheet into a database via a secure web service. Working with the interop was interesting and it was nice to get exposed to something new. I'll post more about web services later (as well as returning custom data types via web service methods, serializable Dictionary objects, and converting rtf to html) but I wanted to rant about the Excel Interop a bit.
Indexing
When I started working with the interop I went in and created an instance of Excel:
This is all well and good. After that I set visible to false and went about starting to access some of the bits and pieces in my xls file. Oh, and because we're using C# and because Open isn't overloaded:
Now, if you run this code you'll get an exception citing an invalid index:
If you're like me you'll immediately try starting your index off at 1 because I suppose it makes sense to start off your worksheets at 1. As much as this seems like VB and as much as I don't like VB I think I can live with it. So you change the sheet number to one and go on your merry way.
Then you try to access some data in your first worksheet:
Running this code gives you an exception and you want to assume it's because you started your indices off at 0 but it doesn't quite make sense because it gave you a different exception:
Hopefully at this point you immediately try
What bothers me about this isn't the fact that I have to start the index off at 1 rather than 0. What bothers me is that when I tried to open a worksheet with an out of bounds index I got an exception telling me that it was an index issue. When I tried cells that were out of bounds I got a generic exception. The worksheet exception was actually helpful and immediately let me know what the problem was whereas the cell exception wasn't even though it was pretty much the same exact issue.
Killing Your Instance:
I'm not even going to into detail on this as it's been beaten to death already. Killing off an instance of Excel is a pain though I don't think that's really the fault of the interop. You have COM to thank for that one. Essentially you have to make sure that for each Excel COM object you create you're calling ReleaseComObject:
And then calling the Garbage Collector:
I'm not really one to talk in this instance though since my client app still has a bug where when multiple instances of the Excel application are created once I close them out all of the processes go away except for the first one I created. It's really bizarre. See the references section for some links.
Hyperlinks
This isn't so much a beef with the Excel Interop as it is with Excel in general. In Excel you can't have a link within a cell. You can have the entire cell be a link to some location but you can't have the text "click HERE for pictures of my cat" and have only the text "HERE" act as a hyperlink. This ended up saving me some time on the rtf to html conversion but is kind of lame in my opinion.
References:
Here are some of the pages that I found the most helpful when working through this:
- MSDN: Excel Tasks
- Craig Murphy: Excel Interop - killing Excel.exe
- MS Help and Support: Office application does not quit after automation from Visual Studio .NET client
Excel Solutions Forum
Indexing
When I started working with the interop I went in and created an instance of Excel:
Microsoft.Office.Interop.Excel.Application excelObj = new Microsoft.Office.Interop.Excel.Application();
This is all well and good. After that I set visible to false and went about starting to access some of the bits and pieces in my xls file. Oh, and because we're using C# and because Open isn't overloaded:
Microsoft.Office.Interop.Excel.Workbook workbook = excelObj.Workbooks.Open(
filename,
Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value
);
Microsoft.Office.Interop.Excel.Sheets sheets = workbook.Worksheets;
Microsoft.Office.Interop.Excel.Worksheet worksheet = (Microsoft.Office.Interop.Excel.Worksheet)sheets.get_Item(0);
Now, if you run this code you'll get an exception citing an invalid index:
System.Runtime.InteropServices.COMException (0x8002000B): Invalid index
If you're like me you'll immediately try starting your index off at 1 because I suppose it makes sense to start off your worksheets at 1. As much as this seems like VB and as much as I don't like VB I think I can live with it. So you change the sheet number to one and go on your merry way.
Then you try to access some data in your first worksheet:
string str = (string)((Microsoft.Office.Interop.Excel.Range)worksheet.Cells[0, 0]).Value2;
Running this code gives you an exception and you want to assume it's because you started your indices off at 0 but it doesn't quite make sense because it gave you a different exception:
System.Runtime.InteropServices.COMException (0x800A03EC): Exception from HRESULT
Hopefully at this point you immediately try
Cells[1, 1] and don't spend some time trying to figure out what you did to go from a meaningful exception that made sense to a generic COM Exception. If you try 1,1 you'll find that it works and all will be right in the world.What bothers me about this isn't the fact that I have to start the index off at 1 rather than 0. What bothers me is that when I tried to open a worksheet with an out of bounds index I got an exception telling me that it was an index issue. When I tried cells that were out of bounds I got a generic exception. The worksheet exception was actually helpful and immediately let me know what the problem was whereas the cell exception wasn't even though it was pretty much the same exact issue.
Killing Your Instance:
I'm not even going to into detail on this as it's been beaten to death already. Killing off an instance of Excel is a pain though I don't think that's really the fault of the interop. You have COM to thank for that one. Essentially you have to make sure that for each Excel COM object you create you're calling ReleaseComObject:
Marshal.ReleaseComObject(excelObj);
And then calling the Garbage Collector:
GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.GetTotalMemory(true);
I'm not really one to talk in this instance though since my client app still has a bug where when multiple instances of the Excel application are created once I close them out all of the processes go away except for the first one I created. It's really bizarre. See the references section for some links.
Hyperlinks
This isn't so much a beef with the Excel Interop as it is with Excel in general. In Excel you can't have a link within a cell. You can have the entire cell be a link to some location but you can't have the text "click HERE for pictures of my cat" and have only the text "HERE" act as a hyperlink. This ended up saving me some time on the rtf to html conversion but is kind of lame in my opinion.
References:
Here are some of the pages that I found the most helpful when working through this:
- MSDN: Excel Tasks
- Craig Murphy: Excel Interop - killing Excel.exe
- MS Help and Support: Office application does not quit after automation from Visual Studio .NET client
Excel Solutions Forum
Tuesday, September 18, 2007
Joel Spolsky
I know some people don't like Joel Spolsky and I know some people do. I'm in the latter camp and I don't care what other people think. Sure some of the things he writes and posts about aren't of a whole lot of interest to me. But then he'll post things like this and it makes up for all the posts of his that I only skim or skip over.
If you have time go read his most recent post (as of my posting this) here:
Strategy Letter VI
This article was particularly largely for the history of computing and software aspect. Being rather young in the grand scheme of things I don’t have the amount of experience and perspective that he has. Given that fact I appreciate hearing about things like the rise and fall of Lotus 1-2-3 and the changes in the business of writing software as a result of the progression of hardware. It’s even more interesting when patterns can be identified.
I'm not sure what happened to Strategy Letters 1 - 5 but I'm sure they were at the very least somewhat interesting. I may have to go dig those up at some point.
If you have time go read his most recent post (as of my posting this) here:
Strategy Letter VI
This article was particularly largely for the history of computing and software aspect. Being rather young in the grand scheme of things I don’t have the amount of experience and perspective that he has. Given that fact I appreciate hearing about things like the rise and fall of Lotus 1-2-3 and the changes in the business of writing software as a result of the progression of hardware. It’s even more interesting when patterns can be identified.
I'm not sure what happened to Strategy Letters 1 - 5 but I'm sure they were at the very least somewhat interesting. I may have to go dig those up at some point.
Subscribe to:
Posts (Atom)