2008-06-16

Cleaning Temp on logoff

I've noticed that the Temp directory in Windows tends to get a little full, and it doesn't clean itself out. I find this rather odd, as it can eat up a lot of space. While I can't prove that it degrades performance or anything like that, it is a little housekeeping that keeps the more obsessive/compulsive side of myself happy.

There are a couple problems, though. One is just remembering to do it. The other is coming up with a convenient way to do it. If you've ever tried to delete these files by hand, you'll notice that Explorer will spend a lot of unnecessary time enumerating the files it's about to delete, and the first file that's actually in use will cause the entire operation to fail. The easiest way to accomplish the goals and get around the problem of locked files is to use a logoff script that is coded to skip or ignore those files.

Once upon a time, I wrote a VBScript file that would systematically delete each file, one by one. With the dreaded On Error Resume Next statement, I could skip the files that were locked without crashing the script. The added bonus was that I could write code very easily to skip certain files. There is an XP PowerToy that manages 4 virtual desktops, each with its own wallpaper; but it stores the wallpaper for each desktop in Temp, and expects that file to be there even across reboots. The huge drawback to this, of course, is that deleting files one at a time is extremely slow.

I've since decided that I don't use the virtual desktops, so there's no sense in installing that app. And, what I was doing in VBScript could be done with two lines of shell script that runs much faster. Since I always have to look up the reference for batch files and variables and what-not every time I try to create this file, I decided it was time to put it in "extended memory" (i.e. a blog post, where I could find it later).

del %TEMP%\*.* /s /f /q
for /d %%x in (%TEMP%\*) DO rmdir /s /q "%%x"

The first line deletes all files in all subdirectories at and below %TEMP%, without prompting. The second line then iterates all the directories and attempts to remove them, again without prompting.

Getting this to run on logoff isn't something that I'll forget, but for others' benefit, here you go. Note that this is only in Windows XP and 2000; I have not yet figured out the Vista equivalent.

  • Run the Group Policy Management Console (Start, Run, "gpedit.msc")
  • Drill down to "User Configuration\Windows Settings\Scripts (Logon/Logoff)"
  • Double-click Logoff
  • Click "Show Files" to open an Explorer window to where the files go, and copy the batch file there
  • Click "Add" and add the batch file to the script list

I have not yet had an issue with this, but it's always possible something could go wrong, so use at your own risk. :P

2008-06-03

DataGridView style inheritance

Interesting "feature" in .Net Windows Forms 2.0 DataGridView. We have a DGV that is initialized in code. At the top of the initialization code is this statement:

dgv.RowsDefaultCellStyle.ForeColor = Color.Black;
dgv.DefaultCellStyle.SelectionBackColor = Color.MediumBlue;
dgv.DefaultCellStyle.SelectionForeColor = Color.White;

Then, columns are created and added:

DataGridViewTextBoxColumn c = new DataGridViewTextBoxColumn();
c.Name = "Foo";
...
c.DefaultCellStyle.Format = "0.00";
c.DefaultCellStyle.ForeColor = Color.Red;
c.DefaultCellStyle.SelectionForeColor = Color.Red;
dgv.Columns.Add(c);

Guess what? The text is not red. Unless it's selected, then it is red. In order to force the text to turn red, I have to do this, after the DataSource is set:

foreach (DataGridViewRow r in dgv.Rows) {
   r.Cells["Foo"].Style.ForeColor = Color.Red;
}

Apparently, the column's DefaultCellStyle is ignored. Actually, it's overridden by the grid's RowsDefaultCellStyle, which is exactly backwards of what I would expect (RowsDefaultCellStyle and AlternatingRowsDefaultCellStyle should be higher up the override hierarchy, because they're more generic, describing an unbound range of rows, than a specific column's DefaultCellStyle).

What's even more frustrating, is that sorting the grid causes the individual styles to be conveniently forgotten. So, I have to add that same loop (or, to be more proper, move the loop to a function, call it from the DataBind event, and add a call...) to the DataGridView's Sorted event.

This was one of those problems that, once I figured out what was going on, only then was I able to google and find the articles that described it. And apparently it's not new, just news to me. I still find it backwards, requiring a whole lot of extra work to make it behave the right way; and thus I reserve the right to complain.

2008-05-29

Missing SQL Feature: Select Top By Group

There's a common problem in SQL, where you want to take records in one table, and join each of them to a matching record in some other table that has the maximum value in some column, but you need data from several columns in that second table.

Let's see if I can come up with an example. Say you have a table of Sales Reps, and you have a table of the Sales they've closed, including the dates and amounts of sales. You want to write a query that shows the Reps' names, and the amount of their largest sale. Easy:

Select r.Name, Max(s.Amount)
From Rep r Inner Join Sale s On r.Id = s.RepId
Group By r.Name

But what if you want to include the date of the sale? Well, now it gets much more complicated. One common solution is to use a subquery that just gets two columns:

Select RepId, Max(Amount) MaxAmount
From Sale
Group By RepId

...and then join against that, and join the Sales table to that on the Amount. Where it falls down, though, is when the maximum is duplicated. What happens, for instance, if a certain rep happened to close two million-dollar deals as his top sellers?

You have to create an additional subquery that finds the maximum Id that matches each RepId and Amount, join on that, and then join on the Sales table to get the rest of the record (the Date field, in this example). The mess looks like this:

Select r.Name, s.Date, s.Amount
From Rep r
Inner Join (
   Select RepId, Max(Amount) MaxAmount
   From Sale
   Group By RepId
) MaxSales On r.Id = MaxSales.RepId
Inner Join (
   Select Max(Id) MaxId, RepId, Amount
   From Sale
   Group By RepId, Amount 
) MaxIds On MaxSales.RepId = MaxIds.RepId And MaxSales.MaxAmount = MaxIds.Amount
Inner Join Sale s On MaxIds.MaxId = s.Id

Wouldn't it be far more intuitive and easier if you could do something like this:

Select r.Name, s.Date, s.Amount
From Rep r
Inner Join Top 1 Sale s Order By s.Amount On r.Id = s.RepId

I found this suggestion on a blog by Raymond Lewallen here. It does leave a bit of assumptions on the SQL compiler, like having to do an order by subgroup on the items in the join clause; perhaps those fields would need to be specified (kind of like how you have to specify everything in "GROUP BY" whenever you use an aggregate, instead of it assuming that it should just by nature of being in the SELECT clause).

2008-05-26

Dreamworks DVDs suck

Time to rant about Dreamworks DVDs. We just picked up Shrek the Third, and although not as bad as Shrek 2, it is still annoying.

My biggest complaint about Shrek 2 is that, when it starts, it launches this really long preview bit with Ben Stiller about Madagascar. But the DVD is authored such that you cannot press the skip-forward button or the menu button to skip it! Fortunately, fast-forward is not disabled. But it still annoys me that I can't instantly skip over it. I've seen this stupid documentary enough. I even have the movie on DVD; I don't need or want to see a promo to learn about a movie I've already seen.

Shrek the Third isn't as bad, although you do have to sit through the Dreamworks logo twice before getting to the menu, and the three previews can be skipped with the skip-forward feature (but not the menu button — a common annoyance with many DVDs).

If possible, I'd love to get a DVD player that does not respect the flags that say you cannot use features of the DVD player, like fast-forward, chapter skip, and menu. Nothing irritates me more than being forced to sit through logos and previews and copyright warnings. In fact, nothing makes me want to break copyright more than being forced to stare at the notice for several seconds every time I want to watch the movie, with every button press greeted with a "feature disabled" alert until the words are burned into my screen.

I wish I knew what this fascination was with the motion picture industry in taking away basic features from people, like DVD specs that include "do not allow" flags and TiVos that can't fast-forward through commercials. It's like they feel they have to balance their entertainment product with annoyance.

2008-05-20

VB RULES!!!

Well, in some cases. I do believe in using the right tool for the job, and, believe it or not, VB was the right tool for this job.

I had a need to convert an NT username to a full name. I found a lot of different, complicated methods of querying Active Directory, but the simplest answer was in VBScript:

v = GetObject("WinNT://domain/username")
v.FullName

As I looked and looked for an easy way to do this in .Net, I discovered that the GetObject function exists in VB.Net. Even better, after a quick trial, the same two lines of VBScript code worked in VB.Net!

Now, as it so happens, I was doing this in a Reporting Services report, which, for editing code, follows in the footsteps of SQL 2000's DTS ActiveX Task code editor — namely, VB in a textbox. What I discovered is, although it works great in the Visual Studio IDE, when I deployed it to the Reporting Services server (even on my local box, where every account is running as Administrator), it failed with a security exception.

The reasoning for this makes a whole lot of sense. Since you can access Reporting Services with a web browser and upload any .rdl files you want, if it didn't apply security constraints to that code, then anyone could upload any code they wanted to run as the Reporting Services account. Great, but how do you get around it when you need to?

The answer to that, I found (although not without a lot of digging), can be as simple as registering an assembly in the GAC, marking it with AllowPartiallyTrustedCallers, having it assert FullTrust permissions, and letting it do the restricted call. The report can then reference that assembly, and all is right with the world again.

Since I had to move this out of Reporting Services's code textbox, I thought I'd try rewriting it in C#. I still had a reference to the VisualBasic namespace to use the GetObject function, but the object i got back was of type System.__ComObject, and from there, I admit, I was stuck. Reflection couldn't get me to the properties, and C# doesn't allow for late-binding.

Could I have solved it in C# eventually? No doubt. But how long would it have taken me? This is my last day on this project, and I'd like to get things done; so if I can get it done by adding an assembly consisting of 15 lines of VB code in the GAC and call it a day, I have to wonder: why bother with anything else?

Imports System
Imports System.Security
Imports System.Security.Permissions

<Assembly: AllowPartiallyTrustedCallers()>

<PermissionSet(SecurityAction.Assert, Name:="FullTrust")> _
Public Class ADLookup
 Public Shared Function GetFullName(ByVal NTName As String) As String
  If String.IsNullOrEmpty(NTName) Then Return String.Empty
  Try
   Dim s As String = String.Format("WinNT://{0}", NTName.Replace("\"c, "/"c))
   Dim x As Object = GetObject(s)
   Return Convert.ToString(x.FullName)
  Catch
   Return String.Empty
  End Try

 End Function
End Class

Reminds me of doing .Net 1.1 code, importing the VB namespace to get at simple functions like IsNumeric and IsDate (tryParse didn't exist for many classes then, and at the time I wasn't experienced with try/catch, certainly not enough to rely on it for intentional testing of data).

2008-05-19

One page, a half-dozen files

Just had to continue on my SharePoint rant. Today, I wanted to add a new page to our SharePoint project. The goal of this page is to get a PDF file from a web service, and download it to a browser. Originally, I had this built in to the postback event of the web part that let you request this file, where the event set a property such that, in the Render method, it would then grab the HttpResponse object, clear it, set the Content-Type and Content-Disposition headers, write it, and end. This worked great, causing a download box to pop up to download the file, except there was a rather peculiar side-effect: after this, none of the events on the control would respond anymore. You could click on any link or button, and nothing would happen.

So, I figured this viewing page had to be separate from the existing page. Instead of a postback, it'd be a hyperlink with a _blank target and an appropriate pair of querystring parameters to trigger the correct report to open in a new window. So I created a brand new web part. Then I created the .webpart file, and the elements.xml and feature.xml file. Then update the manifest.xml file and the .ddf file. Finally, (after wondering where the heck my web part was and calling another developer over to give me another pair of eyes), I updated the deploy.bat file. The web part, plus six other files — and that doesn't include creating the .aspx page in the document library to host the web part (a process that I still have to do manually, since I don't yet know how to add it to a site definition or template or whatever the heck it is).

Let's compare this process to standard ASP.Net. Create the page, which creates the code-behind and designer files automatically. And then... oh wait, no "and then"!

2008-05-06

Mentadon't

I've finally had my fill of Mentadent toothpaste.

If you're not familiar with the product, it capitalizes on the idea that there's a dental benefit to brushing with a combination of baking soda and peroxide. The toothpaste is packaged in these rigid, two-chambered packages. You insert one into a dispenser, and as you push down on the top, two plungers push up on the two chambers, dispensing equal amounts of a blue and a white gel, presumably a baking soda and a peroxide formula.

That's the theory, anyway. In practice, it doesn't always work as smoothly as all that. Typically, because of an air bubble or something, at the start of a new package, one side will be short relative to the other, resulting in a couple days of all blue or all white. That, in and of itself, isn't too bad (except that it's usually the white side that's short, resulting in all blue, and the blue doesn't have the fresh taste that the white does).

The past two packages have been worse than normal, though. The former, the blue side had liquefied. Instead of a blue gel, out came a blue watery substance. I don't know if this was good or bad by itself — we did brush with it anyway, and for all I know probably ended up rotting our teeth with it — but when blue liquid started to show through the seams of the package (obviously designed to contain a gel, not water), it had to go, barely used.

The latter has seemed to defy laws of reality. For the entire life of the package, when dispensing the product, a good 3-4 inches of blue gel will come out for every half inch of white. I can understand this happening if they were filled unevenly, at the start of the package, but I would think at some point, eventually, it would even out. Unless I've been applying uneven pressure while pushing down on the dispenser — but since the thing is almost empty at this point, that kind of uneven pressure would eventually result in something like a 30° tilt.

The hassle just isn't worth it anymore. Sorry, Mentadent, but I just want a tube of toothpaste. This is just too complicated.

2008-05-04

You have two weeks to correct our mistake

I got a letter from the Colorado Department of Motor Vehicles. It seems that when they issued the title for my Toyota Prius, they gave it a fuel type of "Gas", whereas they should've given it "Electric/Gas". I wouldn't think it matters much; technically, the thing does run on gas. The only energy I put into the thing is unleaded gasoline. The fact that it uses that gas to power an electric generator and, in the end, transport me about 50 miles per gallon of gas used is nice and all, but it does all run on gas. If I didn't give it gas, it would be dead. (Barring any conversion process that may allow me to plug it in, but technically the same could be said for any car; just that the "conversion process" may be much more involved.)

But if the Colorado DMV wants to make the distinction, I have no problem with that. What I do find a little interesting is that, along with the convenient "no postage necessary" envelope to return my title, they gave me a deadline: "Please return the title to this office in the enclosed envelope by May 13, 2008." I've owned the car for four years, and now it's imperative that I get this done within two weeks? What would have happened if I were on vacation?

Not that I'm too bothered by it. The two week deadline might just be an arbitrary date they put on there just to get me to return it. And, that's probably not such a bad thing. If there were no deadline, just "return it someday", "someday" could easily turn into "never" as it gets put off indefinitely.

Still, the implied "Hey, we screwed up, now hurry up and act to fix our mistake" almost makes me want to wait until May 14th to send it off.

Global Warming - Lastest Excuse for the War on the Family

Pretty interesting article on how the pseudoscience of global warming is being used in a renewed attack against families and religion. I'll have to keep my comments to a minimum, because not only does the author already make plenty of points clear, I don't think I could comment further without wanting to tear into the lefty environmentalist whackos who actually believe that tripe.

I love the author's last paragraph, though. Reminds me of the Mystery Science Theater 3000 short "Mr. B Natural", when Tom Servo quips, "Meanwhile, the Midvale police visit his locker to find out why they call him, 'Buzz'."

I always found it interesting (and sad) how words get twisted. Instead of perversion, it's "progressive". Obscene is "adult". And conservative faith and belief is "backwards", "Puritanical", and even "dangerous".

I've said it before. Some days, I wish the Second Coming would hurry and get here to put an end to this madness.

2008-04-16

Web Part Madness

In my earlier post, I commented on how, from what I heard from another developer's rantings, web parts involve developing your own web page. As I'm about to dive into my own web part, I realize I didn't fully grasp what he meant.

It appears that some large things that one would normally take for granted in an ASP.Net web page — in particular, the creation of child controls and the rendering of the page and said controls as HTML — must now be coded manually. Yes, imagine my surprise when I opened up some web part code and saw the CreateChildControls and Render methods and all their code.

What. The. Crap.

It's like SharePoint development takes all the ease and safety of .Net coding and rips it away like a scab that hasn't fully healed, leaving a bloody festering wound in its place. But at least they give you a needle, and if you're lucky, you can go out on the web and find enough thread to sew the wound closed.

Yes, the geezer in me is already telling me to sit down and shut up. I've drawn out web pages by hand before, where the closest thing we had to a "server control" was an include file that wrote an expected structure of HTML code to the response stream. But for heaven's sake, that was over five years ago. We're supposed to be well beyond that. We're even coding with the very tools that take us beyond that, and we're being told that we have to do our jobs with the important tools tied behind our backs.

Maybe this is what Microsoft meant by "do more with less"?

2008-04-10

SharePoint, addendum

Just a quick little addendum on SharePoint. As part of my document library woes from before, I discovered my problem. It seems that there are differences between sites and webs, and in some cases, SharePoint blurs the lines between them, and in others, the lines are very clear. For instance, a Document Library site (or web — see, I'm already confused) that exists under the main site at http://localhost/Docs is not the same site as http://localhost/. Although some features are accessible through the web services located at http://localhost/_vti_bin/*.asmx (such as finding a document by URL), some features are not unless you use the services belonging to the specific site (e.g. http://localhost/Docs/_vti_bin/*.asmx, such as creating a folder).

It doesn't help that, when you add a web reference in Visual Studio to a subsite's services by directly entering http://localhost/Docs/_vti_bin/Dws.asmx, it adds the value to the app.config/web.config file at the root http://localhost/_vti_bin/Dws.asmx level, and if you're not expecting this, you're using the wrong services.

Not that all my problems are solved. Apparently, the web services aren't letting me get information on an empty folder, which sucks, so I dropped that possibility and moved on. (Compared to the object model, the web services are pathetic.)

Recently, I created a custom view for a document list. To create this view, I had to download and install the newest version of FrontPage of all things, called the SharePoint Designer (because Visual Studio refuses connect to a SharePoint web site with an explicit message saying so — more evidence at the lack of support in SharePoint for developer tools). I did this because I needed to remove all the "chrome" from the list view (it's being embedded in an IFrame from a CRM site), and the only way to do that is to customize the .aspx code itself, and the only tool to do that is FrontPage (by whatever name they call it), apparently.

With it all created and working great on my local development machine, all that's left is to migrate it to the servers. I have yet to figure this part out. Although we do have manifests and such set up for web part and event listener deployments, we do not have anything for a custom view template page, and so far Googling has failed to reveal anything that might help.

Again, I iterate how frustrating and ridiculous this all is as a developer. I should not have to waste hours of time trying to find out how to move my work from one server to another. It's possible to point my local copy of SharePoint Designer to the server (with some hosts file editing, since the URLs are only created local to the machines), but this kind of manual copying, essentially duplication of work, is also pretty dang frustrating.

I did come across tools that promise to migrate whole lists, but that's not what I want to do at all. That would overwrite content, which would be bad. I just want to migrate changes, update a view (actually the template for a view), and preferably without installing or pointing a development tool to each server in turn for each change. (And I wasn't able to determine if the solutions I came across would migrate views anyway...)

I'll try to keep these posts updated with any solutions that I find to this and other problems as they come up... >:(

2008-04-04

I'm a developer, and I hate SharePoint

When you develop a regular ol' web site in Visual Studio, you just say "create a new web site". You create your pages and web controls, classes, whatever you find necessary. To debug it, you set your breakpoints anywhere you want and hit F5. Studio these days even launches its own built-in web server, so you don't have to set up IIS. You can even change your code on the fly in some cases. When it comes time to deploy, you grab the code, copy it to the server, point IIS to it, and you're done. (Ok, it's rarely that simple, but it can be. Throw in a few web.config changes or other optional steps like pre-compiling if you wish...)

How easy is it to develop a SharePoint site? First, you have to install SharePoint. While I've never actually done this step, I can't really say that it's hard or easy; however, since IIS comes pre-installed, it's a pretty simple bet that installing anything is more difficult than not having to install anything. Considering how big SharePoint seems once you get it installed, I imagine installing and configuring it can be extremely complicated. Or perhaps it's fairly easy if you just accept all the defaults.

I'm working on a SharePoint project that, so far, we've tried building an event listener, a web part, and code that uses the web services. The event listener works fine, except that to debug, you have to:

  1. manually create the list in the SharePoint site on every developer's instance of SharePoint
  2. register the listener class library in the GAC
  3. use a command-line utility to register the listener with the event
  4. run IISRESET to restart SharePoint
  5. attach the Visual Studio debugger to the SharePoint process

Fortunately, a batch file takes care of the middle steps, and the first step only has to be done once. Still, it's a lot of extra overhead, and it has to be done for every listener that gets created.

The web part has been the responsibility of another developer. Based on his comments, I gather that designing a web part involves practically designing a whole web page. What I remember from them in a previous job, setting them up and deploying them was a royal pain. Modifying them was considered something you just Didn't Do. Their solution was that they had a web part that had, as a parameter, the path to a web control. If you wanted to create a new element on a web page, you created a regular .ascx web control, dropped the "container" web part on the page, and set the path property to the location of the .ascx control. Probably the only easy thing about that job.

The web services are supposed to be easy to use. They have been decidedly not so. My tasks have revolved around trying to interface with the default document library. I can access documents by using the URL http://localhost:ip/Docs/Documents/filename.doc just fine. And yet, if I try to get any information about the document library itself, it tells me the very document library from which it is serving documents doesn't exist. This is most evident when I use the Dws.GetDwsData method on an existing document. It returns a load of information (including a "LastUpdate" parameter which, instead of being a piece of useful information like when the document was last updated, is a key for future calls to see if it was last updated since a given key). One of these pieces of information is a reference to the Document library, which it returns as an error code, "ListNotFound". Because this document library that these documents are stored in doesn't exist, I can't use other methods I need, such as Versions.GetVersions, Dws.CreateFolder, or Lists.GetListItems.

And we haven't even gotten to the point where we try and deploy SharePoint content yet. As I already alluded to before, there's no real way to transfer content pieces from one machine to another in any easy or automated way. A simple list had to be manually created on every developer's machine, and it'll have to be done on Development and Test. In that previous job, they had an established procedure: back up the entire SharePoint database from development, and restore it in the test environment, then QA and then Production in turn. (Which, yes, meant you can't store any development or test information in SharePoint.) While I was there, for the first time, a SharePoint List was used to store data instead of content, and they had to bring in a Microsoft consultant to write a special utility to export the data and re-import it, so it wouldn't get lost in the complete-backup-and-restore deployment procedure they had for years. Because, of course, no procedure currently exists.

Supposedly, SharePoint is what companies want, because it means they can manage content without having to go through development to do it. Case in point, we have a static web site that we're moving into SharePoint for our client, because every change they want to make, they have to call us to make it. The problem is, it's taken our one consultant, the one who's supposed to be experienced in SharePoint, all week to get the existing site into SharePoint, and he still hasn't even gotten the framework in place, let alone the content — something even he agrees would take maybe two days, start to finish, to completely rebuild in a standard ASP.Net site.

Where it seems to make sense to set up a single-environment easy-to-manage web site and collaboration area, it is a nightmare for multi-environment (i.e. Dev, Test, Production) migration and custom development. And yet, it's what the people at this consulting firm keep selling like it's the Holy Grail. I have my own theories as to why this is, but the fact remains, we're stuck with a tool that just refuses to do the job, and tasks that should take half a week are becoming impossible to do in two.

2008-03-24

Whatever happened to Microsoft Phone?

Many years ago, I had access to a piece of software called Microsoft Phone. It was distributed with some versions of the Creative Labs Phone Blaster, a rather massive expansion card that combined a modem with a sound card. What made this combination really cool was that the card and the software turned your computer into a telephony device. It wasn't the first or only device on the block that could turn your computer into an answering machine — I had something in my computer in college that did the job as well (although I'm sure my roommate often times wished we just had a simple tape-based machine, especially when I would reboot my computer and it would knock him off the phone). It also wasn't the only software that could use your computer's microphone and speakers as a speakerphone — some Phone Blasters were bundled with completely different software that did that as well.

What made Microsoft's product so interesting is that it was integrated with its fledgeling text-to-speech and voice-recognition programs, and it used the common MAPI message storage system. Back in the day, when Windows 95 was still new, mail storage could be set up as more of a common database. At least, the mail storage location was a simple control panel icon, and the built-in Windows Mail client could connect to the MSN service without an issue, without having to use any custom email software. (It seemed simpler then; maybe it could still be done that way today, since I'm back to using Windows Mail in Vista instead of Outlook or Thunderbird or Outlook Express or even Live Mail.)

In any case, installing the Microsoft Phone software included installing Microsoft Voice, which allowed for some voice recognition. Trying to control the computer with it was more of a gimmick than being really useful at all. (Looking back on it now, it's hard to expect more out of 1995 technology, although I'll get into that in a minute.) The command set was fairly limited, although what commands it did know, it did recognize fairly well, so although there wasn't a lot you could do with it, you could at least do those things consistently.

Now, here's where things got interesting, as far as Microsoft Phone was concerned. One of Phone's features was that you could dial in to your phone and enter a code to access the program and start issuing commands. Fairly standard fare for answering machines. And, just like any ordinary answering machine, you could issue those commands by using a touchtone keypad. Where it started to set things apart was, Phone would prompt you for commands, and not by using pre-recorded prompts. It would read you instructions using text-to-speech reading from a help file. While this might not sound like such a big deal, especially in an age where sound compression and disk space are cheap, back then, this was huge. Phone was able to provide a rich, vocal interface without having to save megabytes of prerecorded files.

The next interesting thing it could do, because of text-to-speech, was it could read extended information about your message. It could not only announce the time and date of a message, and read the phone number, but it could also read the name of the caller. If that caller was in your address book (remember, this was all coming from your MAPI store), it could read that personalized name. Granted, the pronunciation wasn't always perfect, but it was still a very cool feature to hear your computer read the name of your caller to you over the phone along with the message.

Now, this was back in the days of dialup, and you could set your computer to call into your ISP and check and download email during the day. This brings me to the next very cool feature. Remember, all messages were stored in the single MAPI store. So when you play new messages, included in that was your computer reading your new email messages to you. The first time I showed this feature off to a friend, her jaw hit the floor as soon as she realized what was happening.

And finally, because this was running with the Microsoft Voice speech recognition engine, not only could you give the commands using your touchtone phone buttons, you could speak your commands and have the machine respond.

So where is this technology today? As I was writing this, I was thinking about how cool this was back in 1995, but I was questioning how it would work a decade later. In 1995, voice was still a preferred method of communication. Being able to call my computer and have it read my email to me would be a great feature. Today? Not so much. In 1995, 99% of email in my inbox was interesting and valid, came from human beings who sat down and thought about what they wanted to say, and wrote it as if they were writing a letter, with well-thought-out sentences and grammar. Today, most email is hastily dashed off, with lots of abbreviations, written with very little context. And that's only the ones I want to read, which is a minority. The majority of email I get now is mass mailings from corporations, or spam about prescription drugs I don't need or software that's not licensed or legal, or newsletters or chain letters or group mailings or any number of other impersonal communications — basically, nothing I'd need to phone home about.

However, the concept of having voice messages delivered to email is desirable. So I guess, a decade later, I'd be looking at the reverse of what was cool in 1995 — being able to connect a text-type device (i.e. an email browser) to my home message storage and get voice messages. It might be cool to have dictation transcribe that to text for very low bandwidth applications, but with the way email clients work these days, it'd almost be unnecessary. (Almost. I still use Pine over an SSH connection to read email from work, so text-only email and browsing isn't dead yet. At least not for me.)

I'm still intrigued by the concept of a software-controlled answering machine, one that could take messages, convert them to email (making the "from address" look like a phone number would be a nice touch), especially one that could be programmed to automatically answer and annoy calls from certain phone numbers (such as those identified charities that continue to call soliciting donations despite the request for no solicitations). In the end, we've got to go with something that "just works"; so although I tried to recapture those old glory days by buying the Microsoft Cordless Phone product around the turn of the century (that failed to live up to expectations, especially when it wasn't supported past Windows 98), we ended up buying a plain old telephone answering machine that isn't a piece of software installed on a computer — and my current roommates (i.e. my family) are much happier for it.

As far as voice control, I finally decided to play around with it in Vista the other day. I will say that I am impressed with how far it has come. For general navigation, there's this "say what you see" concept, where you can just say the name of what you see — the name of a link on a web page, for instance — and the computer will attempt to discern what you mean. You say the name of a command button or link, and it clicks it. However, it's still rather clumsy. I feel like I'm talking to my 1-year-old, having to repeat things over and over again, sometimes louder, sometimes trying to say things a different way. Dictation was especially frustrating, as you're supposed to be able to say "correct" and the words it just got wrong in order to fix it — and yet over and over again, as I was trying to dictate and correct, it started typing "correct this" and "correct that", like an old sitcom routine where the dullard keeps reciting stage directions.

And in that sense, it doesn't feel much different than 1995. Sure, you can actually dictate text now. But giving commands? That's been around for over 10 years, and we're still pointing and clicking with a mouse. At the end of the day, I couldn't see how it could possibly be any more convenient or useful than just using the freaking mouse or keyboard to directly input the location or text desired. Even if it does entertain my wife to listen to me try to use it....

2008-03-17

It's so hard to send email programmatically

One look at the subject line, and I'm sure the first word that comes to the minds of the four people who might see this blog is, "Huh?" Sending email isn't hard. Programs do it all the time. Why can't a genius like you figure it out, Yakko?

Well, here's the problem. See, I'm writing a custom Windows application for a client. This application is essentially a database for storing the deposits and withdrawals of a handful of investment companies and their investors. In essence, it's copying what he tracked in spreadsheets. Since I'm moving this from spreadsheet to database, I can implement all new features for him like tracking performance over time, graph them, compare them to market indices, and the big new feature, generate PDF reports and email them instead of printing paper copies one by one.

He runs all of this on a PC in his home office. As such, I made sure I wrote the program to be as small and simple as possible. Sure I considered using SQL Server Express with Reporting Services to generate the reports, but how easy would that be for him to install, or back up the program, or send to his part-time clerk for easy data entry? Small and lightweight were part of my requirements here. And thanks to the .Net framework, I managed to write a pretty decent app, with a SQL Server CE database engine, that would fit on a floppy disk. Not that I could find a floppy disk to put it on anymore, but you get the idea.

The one part of the program that really disappointed me, though, was the email tool. Here I managed to generate these nice-looking PDF reports in real-time (using the PDFSharp library -- they say "the same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer", and in my experience it really was almost that easy to convert my paper printing code to make a PDF). But how to email them?

While it is true that the .Net framework includes some very simple routines for sending SMTP mail, which I have used before in various client-server, intranet, and web applications, the trick is they work really well if you're using them on a server. Ever send email from a personal computer on a dial-up connection? (Yes, he's still on dial-up; don't ask.) Even if you're on broadband on a static IP, chances are your address is identifiable as an end-user, and there's a very good chance your email will be rejected with prejudice.

Now, he can send out email using his email program of choice. Why? Naturally, because it's configured to use his ISP's email server. Because I want to keep this program as simple as possible, adding email configuration was not in the design; and, I could find no way to query Windows to discover (a) what email program he's using, and (b) what settings that program uses to send email. (I couldn't even be certain I could use the same settings -- he's on MSN; what if it requires SPA?)

When you think about it, it all makes sense. How easy it would be for spammers to write programs to do just that and send email out via a user's ISP's email server, instead of trying to send out from the easily-blockable local address.  If I recall correctly, viruses used to do just that, before email programs had to get better at locking external program access out.

So what do I do? I create a "mailto" link with the appropriate subject and body parameter for every PDF that must get generated (but no attach parameter; although that used to work once upon a time, apparently that got to be too big of a security risk as well, and while some email programs simply ignore it, Outlook will refuse to start and throw an error instead), then delay about 5 seconds (because in some of my test scenarios, if a second "mailto" link was getting processed at just the wrong point of processing of the first one, the second would trigger an error instead of opening a second window). Once all the mail windows were open, an Explorer window would be opened to the folder containing all the PDFs generated, so they could be manually dragged and dropped on the appropriate mail windows.

I ask you: how much does that suck?

Probably the only redeeming point in this is that, at this early stage in the game, manual intervention is desired, so he can see the reports and the messages before they go anywhere. It's going to get old very fast, though...

2008-03-12

It's not a toy, it's for business

My wife and I got Tablet PCs for our business. We've been wanting them for a while. We decided to pick up a couple HP Pavilion tx1420us models. These are considered "home entertainment" models, not business models, but they had decent specs for the price.

The screen isn't as big or as fine as I would like. This replaces a Thinkpad T40p, which had a nice large screen and a 1400x1080 resolution. This tablet has a relatively small screen with a lot less real estate at 1200x800. Also, I've found that the pen takes a lot more pressure to use than I would like. It's quite tiring to write with for long periods of time, and it has a tendency to "stutter" when drawing over any distance.

But I definitely like the tablet. I've wanted one for a long time, and I'm glad I have one. They are fun to use, not to mention pretty easy when "point and click" is literally pointing with a pen and physically tapping it. There's something rather satisfying about being able to move things around screen by grabbing it with a pen and pulling it around. It makes it feel a little more "hands-on" than like I'm using a remote control (i.e. the mouse or touchpad).

It comes with Vista, which at the moment I have this sort of love/hate relationship with. I know the UI enhancements are all eye candy, but honestly it makes the computer "feel" next-gen, like we've finally progressed. However, it is very different than older versions of Windows. When I first sat down in front of XP, it didn't take long to find my way around, because it was so similar to Windows 2000, which was similar enough to 98... In Vista, I'm still trying to figure out how to do tasks that were second-nature to me before. So far, though, it hasn't been "too bad". I haven't run into any driver or compatibility problems, and I found the dialog box that pops up every time you launch a program easy to turn off (convenience trumps security, for now). I'm sure a big part of this is I'm running on all-new hardware that came with Vista — I still have no intention of ever trying to upgrade my older machines to this OS, even if they're still running long after XP is dead and buried.

2008-02-29

How fast is a parsec?

Possibly the most plausible explanation I've heard for the blatant error in unit in the cantina scene in A New Hope is, according to a copy of the script, Skywalker and Kenobi were supposed to react as if they knew they were being fed a load of hogwash — an emotion that was, unfortunately, never really conveyed.

The Wookieepedia article on the Kessel Run mentions this, plus another explanation offered by George Lucas in DVD commentary (that he meant his navigation computer could find shorter — and therefore faster — routes), but only as "behind the scenes". The more prominent explanation is given thus:

Solo was not referring directly to his ship's speed when he made this claim. Instead, he was referring to the shorter route he was able to travel by skirting the nearby Maw black hole cluster, thus making the run in under the standard distance. However, parsec relates to time in that a shorter distance equals a shorter time at the same speed. By moving closer to the black holes, Solo managed to cut the distance down to about 11.5 parsecs.

Essentially, his ship was so fast, he was able to take a more dangerous shortcut.

Why do I think this is a load of bunk, in the context of the cantina scene? The whole conversation at that point was about speed. Why mention distance when your client is asking about speed? Expecting a farmboy and a nomad to know the details about a particular space race as a condition for winning a fare seems like pretty bad business sense. Solo may have made some bad decisions, but I don't see him as being entirely stupid.

If you do take the Wookieepedia explanation as fact, though, it does explain that awkward silence with the following deleted scene:

Han: "It's the ship that made the Kessel Run in less than 12 parsecs."
Luke: "So, that would mean he'd have to take the Maw shortcut? How fast do you have to go to get around there?"
Ben: "Um, let's see, take a left at Geonosis Prime, you'd have to circle Kamino..."
Luke: "No, Kamino's too far; that's at least half a parsec out of the way; he'd have to go past Denobula."
Ben: "Denobula? Oh, yeah, at the Circle-K. Used to love their donuts. I haven't had their donuts since, oh, before you were born..."
Luke: "So, to get down to 12 parsecs, you'd have to cut pretty close to Naboo in order to make that turn."
Ben: "That would put you within... ten million kilometers of Maw?"
Luke: *scribbling* "No, no, carry the 4."
Ben: "Oh, right, right, half a million kilometers. Now, with the mass of Maw, the velocity needed for a Corellian freighter to escape would be... um..."
Han, annoyed, impatient: *ahem* "She's fast enough for you, old man."

Assuming Han was full of it, though, and Ben and Luke eventually caught on, I imagine the scene in docking bay 94 might've played out a little differently...

Luke: "What a piece of junk!"
Han: "She'll make .5 past light speed."
Luke: "What, .5 parsecs?"
Han: "She may not look like much, but she's got it where it counts."
Luke: "Really? Does it count all the way to blue?"
Han: "I've made a lot of special modifications myself."
Ben: "Yeah, like the ability to tell time with a ruler."
Han: "But, we're a little rushed..."
Luke: "Yeah, we've got to go in just a few inches."
Han: *thinking* I should've let Greedo shoot first...

2008-02-21

There's always a bigger fish

I've been on the job market for a little while now. I got laid off from my previous job, thanks to recent changes in the economy. Fortunately, the job market is fairly active at the moment. Inside of a week of finding out I was being laid off, I had two phone screens and an in-person interview.

One company with which I interviewed recently, I felt was going to be a sure thing. The phone interviews went very well, and when I met them in-person, everything seemed to go well. I had a great conversation with the manager, and the interview with the development team was great. I felt like it was a great team, like it was a great group of guys to work with, and that we would get along wonderfully. They seemed reasonably impressed with what I knew and what I could bring to the company as well.

I did have another company that was wanting to bring me on. They had worked with me before, they knew my capabilities, and I think they were nervous that I might take another offer (I had done it before — no hard feelings to them, but at the time, they didn't have anything for me, and I had another offer that was really good). I wasn't going to tell them no until I knew I was going to take another offer, so of course I was eager to see if I was going to get one from the interview. (I was pretty sure I would, but I did want to be sure the offer was in the ballpark of my desired salary/benefits.)

This is where it helps to be working through a recruiter. I can call him and pester him all I want, without fear of turning off the potential employer for calling them directly too much. ;)

I finally got their decision. They said I was a great fit and fully qualified, but they were going to go with someone who had just a little more expertise or experience or something. Now, fortunately, I did have interest from the other company, so I am able to switch over, accept, and start working on this other project. Even if I'm not sure it's ideal, the fact is, it's work, and as such, it's income. (There are also other benefits, such as a decent health care plan, not far from my kids' school, more of a "known quantity" as I've worked with them before...)

The annoying thing is, this isn't the first time I've heard that explanation. "Yeah, you'd be perfect, but someone else is better." It just leaves me wondering. Is it the truth? What is it that I could've improved on so that I could be the one that's just "a little better"? Or was there a flaw with me that they're not telling me? Or was this other guy's advantage more of an inside — a friend or relative that as good as had the job from the beginning? (Although usually it's the bigger companies that bother with interviewing just to say they did; this was a smaller company.)

Then again, I had been praying that I would be guided to the right job for me and my family. Although I think I would've enjoyed the work, it was a bit of a commute, which would've taken me away from my family. Perhaps that, or other reasons that I couldn't see, would've made this job not as good as something else.

In any case, it's hard to be too upset. Instead of having to make a difficult decision between two opportunities, the options narrowed themselves down for me. It's almost more of a relief, actually. And, I now know where my next few paychecks, health insurance, and so forth are coming from beyond the next two weeks. I know my wife can relax a bit, too, knowing everything's pretty much taken care of.

2008-01-16

ASP.Net, Dynamic Controls, and ViewState (oh my)

Let's say you have an ASP.Net page. On this page is a PlaceHolder. In that page, controls may get dynamically loaded into the PlaceHolder. This may occur as a result of a postback event (e.g. Button or LinkButton click) or simply as a result of some logic that occurs when the page loads. However, even with EnableViewState set on the PlaceHolder, controls that you load are not automatically persisted in ViewState. What to do?

This is a problem I've run into at least a half dozen times in my .Net coding career. It's also a problem I've solved that many times. However, every time I come across it, I forget one small detail, or I do something a little differently that makes it all fall apart. So, at least for my own benefit and perhaps for the benefit of others that may stumble upon this post, I'm putting the solution here. I haven't yet seen anything that pulls all these techniques together; this might not be the first, but at least it's one I know. Links to other pages contained here may not be from where I first got my information, but I'm including them for further reference and information.

First: When you create your controls, make sure you add them to the page FIRST, and THEN set any properties on them. This is because, in order for the ViewState manager to consider the control for management, it has to detect a change, and it can only detect changes after the control has been added to the Controls collection. (Thanks to Jeffrey Palermo's blog for pointing this out, and explaining the reason for it.) But what if you don't have any properties to set? That is remedied by the next consideration.

Second: Set an explicit ID. When ViewState is saved and restored, the control IDs must match. (I've seen a reference that says you have to use a custom class and explicitly tag it "ViewStateModeById", but my experience has shown me that ID matters whether you do this or not. Sequence may or may not be as important.) If you don't specify an ID, ASP.Net will automatically generate one, and there is no guarantee that the one generated when you create it on the fly will be the same when you recreate it on PostBack. The result is, you may get the control back, but its state will not persist on the first PostBack (although it might on the second and subsequent PostBacks, since it will be consistently reloading during the PostBack from the same spot -- this behavior may be confusing).

The tricky part comes in play when you have to reload the controls on PostBack. You have to do it before ViewState gets loaded, otherwise the controls won't be there to get their ViewState restored to them. The event before LoadViewState is Init. But you have to somehow have the page remember what controls were loaded. Keeping that value in ViewState is the most convenient, but you can't retrieve that value from Init because ViewState hasn't loaded yet. What to do?

I've seen one solution to this as to use the Session. I'm not a big fan of this myself for a few reasons. First, it requires Session. I consider it a goal to write web applications that don't require Session at all, because they generally perform faster and are more scalable. Granted, I rarely succeed, and Session does have its place, so writing it off isn't a valid enough reason. Second, it is very easy to get the page state out of sync with the session state. Hit the "Back" button a couple times, and then submit. The page submits a ViewState with two controls, but the server checks the Session variable and loads four controls, and then tries to apply the ViewState to it. Third, that variable has the lifetime of the Session. At worst, if it's not cleared out or checked appropriately, its presence may cause confusion on another page. At best, it's just taking up a couple bytes of memory long after its purpose has been served.

So what's the solution then? Third: My trick is to override the SaveViewState and LoadViewState methods on the page. SaveViewState returns a serializable object that gets dumped to the page. It's fairly trivial to override this method to return an object[2] (which is still an object and still serializable), putting your own data in one element and the results of base.SaveViewState() in the other. What you need to store in that element is something you can use to recreate the controls in the order they exist on the page. Maybe it's just a number -- if you create the controls as a series and give them IDs of ID{x}, where {x} is the sequential number, then all you need is the number of controls to recreate. Or maybe it's a string array that contains the list of control IDs. Or perhaps, if the controls are of different types, it's an array of the control types that have to be loaded, or the paths to the .ascx files. Whatever it takes.

The LoadViewState method is overridden to check to see if the ViewState object is an object array, and if the first element is of the type we expect (the one we create in SaveViewState). If so, it recreates the controls based on that first value, and then calls base.LoadViewState on the second. (If it turns out not to be an object array or something is not of the expected type, I am in the habit of just calling base.LoadViewState directly, just as a safety catch in case the overridden SaveViewState got missed. It's never happened yet, though.)

The code, therefore, looks a little like this:

/// <summary>
/// Saves the view state, including the IDs of placeholder controls so they can be reloaded on postback
/// </summary>
/// <returns>new ViewState object</returns>
protected override object SaveViewState() {
    object[] newViewState = new object[2];

    List<string> docTypeSelectionControlIDs = new List<string>();
    foreach (Control control in placeHolder1.Controls) {
        if (control is DocTypeSelectionControl) docTypeSelectionControlIDs.Add(control.ID);
    }
    string[] arrayOfIDs = docTypeSelectionControlIDs.ToArray();

    newViewState[0] = arrayOfIDs;
    newViewState[1] = base.SaveViewState();

    return newViewState;
}

/// <summary>
/// Loads the view state, including custom state information from the SaveViewState override
/// </summary>
/// <param name="savedState"></param>
protected override void LoadViewState(object savedState) {
    //if we can identify the custom view state as defined in the override for SaveViewState
    if (savedState is object[] && ((object[])savedState).Length == 2 && ((object[])savedState)[0] is string[]) {
        //re-load the DocTypeSelectionControls into the placeholder and restore their IDs
        object[] newViewState = (object[])savedState;
        string[] arrayOfDocTypeSelectionControlIDs = (string[])(newViewState[0]);
        foreach (string docTypeSelectionControlID in arrayOfDocTypeSelectionControlIDs) {
            DocTypeSelectionControl dtsc = (DocTypeSelectionControl) LoadControl("DocTypeSelectionControl.ascx");
            placeHolder1.Controls.Add(dtsc);
            dtsc.ID = docTypeSelectionControlID;
        }
        //load the ViewState normally
        base.LoadViewState(newViewState[1]);
    } else {
        base.LoadViewState(savedState);
    }
}

/// <summary>
/// Loads the page, including some dynamic controls
/// </summary>
protected override void OnLoad(EventArgs e) {
    if (!IsPostBack) {
        Document[] documents = (Document[])(Session["Documents"]); //The size of this array varies
        foreach (Document doc in documents) {
            DocTypeSelectionControl dtsc = (DocTypeSelectionControl) LoadControl("DocTypeSelectionControl.ascx");
            placeHolder1.Controls.Add(dtsc); //Add control FIRST
            dtsc.ID = String.Format("DTSC_{0}", doc.DocumentID); //THEN set ID
            dtsc.InitializeControl(doc); //control initializes itself based on this document, and saves info in its ViewState
        }
    }
}

Note that this is just a quick sample. The controls in the placeholder could be modified as a result of some control event, but it really doesn't matter. The part that controls saving and reloading the ViewState of the controls doesn't change.

UPDATE 4 Dec 2009 — I created a sample project, using Visual Studio 2005 and .Net 2.0, that probably does a better job at describing things than I'm trying to do here. The project is very simple, with a single page and a single control. The page starts by loading a single instance of the control into a placeholder, and a button lets you add as many more instances of the control as you want. Every time you add a control, there is a PostBack, and all existing controls get their state and values loaded and re-saved to ViewState. You can see that the controls themselves do nothing to save their values from PostBack to PostBack; it all happens automatically thanks to the standard ViewState manager. When you click the "Submit" button, the main page loops through all the controls, collects the selected values, and builds a table to display the results. There is no use of Session, and at any time you can use the Back (or even Forward) buttons to navigate to a previous state of the page and pick up from that point.

DynamicControls.zip

2008-01-07

Come join the Social!

I don't have a Zune, but I thought it might be kind of fun, since your Zune Card and your Gamercard are automatically one, to set up my Zune Card account and join "the Social", as it were.

For a company that usually has "integration" down to a science, they're really dropping the ball on this, aren't they?

I have Windows Media Player and Windows Live Messenger. With a simple flick of an option on both sides, I can have my status message on Live Messenger automatically update to show what I'm listening to in Windows Media Player. It should be pretty much the same thing for Zune, no?

No. I have to download the Zune player. While it has some nice little features of its own (I like the collage of album art you can have in the background of the "now playing" mode), it lacks a ton of other features that are in WMP11, like visualizations, minimizing to a toolbar, updating the Live Messenger status message, responding to the media keys on my keyboard...

I did some searching on the 'net, and all I could find were echos of the questions going through my mind: Why didn't they just make a plug-in for WMP11 and integrate Zune functionality into their already-very-capable and useful media player?

2008-01-06

Water heater want a blankie?

Our hot water has always been pretty hit-or-miss. Some mornings, I would get up, turn the water on full hot until it warms up, and have to back it off to a comfortable temperature; others, I would leave it on full and have a lukewarm shower. I don't know if lately it had been getting worse, or if we just decided to do something about it, but we finally got an insulation blanket for it.

The difference was immediate. The very next morning, I noticed that the time I had to wait before I got hot water was dramatically shorter. And, the water was a lot hotter. It was very nice. Since it had been known to happen on occasion before, though, I was hesitant to directly attribute it to the new blanket; but we installed it on New Year's Day, and for the past five days since, the results have continued to be positive. I've also noticed that water out of other taps (like the kitchen sink, for instance) also get hotter water faster.

So how much money have we thrown away over the years? Honestly, probably not a lot. It's said one of the problems with a water heater is that it has to constantly re-heat the same water in the tank as it sits and cools when it's not used, and it would seem that our water heater didn't do very much re-heating once the water cooled. It's a gas-fueled appliance, and our gas bill during the summer months has always been pretty low. Although add on the amount of water dumped down the drain waiting for hot water to flow, and even if it's still not a lot, it's still waste. Plus, we've been throwing away comfort. Even if it's not a big thing, it's nice to know that we managed to do something to improve our lives. Not only is it an improvement, it helps to take the edge off knowing there are so many bigger improvements that we just haven't gotten around to, or can't afford, yet.