Saturday, September 04, 2004
I've always been a Media Player user; never really getting into WinAmp and MusicMatch - though I've had my share of using them.  Just this morning I installed the latest version of Microsoft's Media Player 10 and I must say I'm very impressed.  I very much enjoy the new look and feel as well as several of the enhancements that were made.  The transition to version 10 from 9 was seamless.  Very cool stuff!
Saturday, September 04, 2004 4:51:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, September 02, 2004

For the longest time I've been writing software (be it small apps, utilities, components, controls, or even large ambitious application) pretty much by myself.  Sure, on a weekly basis I gather with some friends and we have a coding bash and accomplish some great things.  I've never known what to call these endeavors.  Today I made a discovery that impressed me.

While perusing some blogs I read, I found a link to an article by Eric Sink of SourceGear.  He posted an article on msdn.microsoft.com in which he discusses the concept of a Micro-ISV; that is, a small one-man company focused on delivering products.  I feel that I very much fall into this category of Micro-ISV-ness.  Very soon I'll have my products website up with some downloadable products and tools for people to use within their applications.

Eric offers several hypotheses about what it takes to be a successful Micro-ISV.  I wholeheartedly agree with his “Don't start too big” section.  It's far too easy when designing and coding a product to not stick with your initial goals and concepts and allow scope creep to enter the picture or simply to dream too big.  I've done this on more than one occasion...it's a hard habit to break.

Anyway, good article, Eric...I look forward to reading more articles on the 'business of software'.

Thursday, September 02, 2004 6:12:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, August 30, 2004

I'm in the process of writing a custom web control (System.Web.UI.WebControls.WebControl-based class).  This control renders client-side as a DIV tag that allows for rich text editing.  One of the biggest challenges had to do specifically with focus.  This would not have been such a problem had I simplified my UI, but I'm not one to sacrifice functionality for a crappier UI if at all possible as many of my friends would atest.

My control renders a custom toolbar (or set of toolbars).  Each of these toolbar buttons or menu items renders (presently) as a TABLE, though I'm in the process of figuring out how to update my control to use DIVs and SPANs as appropriate, but that's beside the point.  My toolbar buttons are 16x16 images contained within a TD that measures 23 pixels tall by an arbitrary width.  It so happens that if I clicked directly on the image in the button, my DIV would not lose selection and the appropriate action would take place on the entire range.  However as soon as I clicked off the image (but still clicked within the boundaries of the button), my selection would disappear on mouse down and when the click event fired the DIV would have lost its focus and the action would affect just the text at the beginning of the selection.

I spent hours attempting to figure out how to programmatically select the appropriate text within the DIV using TextRanges and what not, all to no avail.  And then it dawned on me that instead of trying to reselect the appropriate range of text, I should instead work on not allowing the selection to disappear in the event that the button was clicked (but not directly on the image).

It was approximately 23 seconds (maybe 24) after this thought occurred to me that I added the UNSELECTABLE='on' attribute to the TD that defined the button.  The UNSELECTABLE attribute is quite handy - when you click on an UNSELECTABLE element on the page the current selection is not destroyed.  It turns out this was an answer to my prayers :-)  Now I just have to finish the control as it's 95% done.

Monday, August 30, 2004 8:27:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [6]  |  Trackback
 Thursday, August 26, 2004

In an ongoing effort to keep Visual Basic .NET within the mainstream, Microsoft is making some additions to the language that will definitely keep it viable moving forward.  I'm excited with this new direction.  Read on!

Thursday, August 26, 2004 7:09:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

For all those “Star Wars“-philes out there, those that are dying to have Star Wars be a reality (aside from the prequel debacles), look no further.  It appears that the Death Star has been found near Yavin...er, Saturn.

http://www.engadget.com/entry/1884557355723102/

Thursday, August 26, 2004 6:50:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, August 25, 2004

I got my first chance to experiment with and quickly get frustrated with InstallShield X this past week.  I've never taken the time to sit down and write an installer, so it was all new to me.  To it's credit, InstallShield is a very powerful package and I am quite pleased with it.  My frustration stems from the steep learning curve.  I knew what I wanted to do, but couldn't figure out how to do it.  Trying to navigate the help is a daunting task.  Why don't companies write help that's helpful?  I had a very hard time figuring out how to accomplish what seems like a trivial task.

I started out creating a Basic MSI project and that got me off the ground and well immersed in the InstallShield world.  However, I quickly found that I was limited with what I could do.  I needed more power.  I therefore resorted to an InstallScript installer.  As soon as the new project opened up I felt more at ease...now this was stuff I could understand!

Ok, long story short (sorry!), I was creating an installer for a web component.  This component gets installed within a existing virtual directory on the user's machine.  I needed to enumerate the websites and virtual directories.  I quickly came to a screeching halt however when I discovered that InstallShield has no mechanism that supports enumeration.  Sure, there's built in support for a few looping constructs (e.g. for..endfor, repeat..until, while..endwhile) but no foreach support.  No way to take a collection and walk the contents.

Fortunately, the all-seeing, all-anticipating wizards at InstallShield foresaw the need for extensibility and allow you to import and dynamically load Win32 dlls and call native functions.  I therefore began the arduous task of creating a C++ dll that provided entrypoints that emulate the foreach loop to which we've become so accustomed in C# and VB.  I received some inspiration from some articles and things I found online - here's my C++ code:

#include "stdafx.h"
#include <windows.h>
#include <comdef.h>
#include <atlbase.h>

#define CHECKHRESULT( expr ) if ( FAILED(expr) ) return expr;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
   return TRUE;
}

long __stdcall ForEachStart(VARIANT *pCol, VARIANT *pVar)
{
   if ( VT_DISPATCH != pCol->vt || 0 == pCol->pdispVal ) return E_INVALIDARG;

   try
   {
      CComVariant     result;
      DISPPARAMS      disparams = {0};
      UINT            argErr;
      pCol->pdispVal->Invoke(DISPID_NEWENUM, IID_NULL, 0, DISPATCH_PROPERTYGET, &disparams, &result, NULL, &argErr);

      return result.Detach(pVar);
   }
   catch(...)
   {
      return E_UNEXPECTED;
   }
}

long __stdcall ForEachNext(VARIANT *pEnum, VARIANT *pVar)
{
   try
   {
      if ( !pEnum ) return E_NOINTERFACE;
      if ( pEnum->vt != VT_UNKNOWN || pEnum->punkVal == NULL ) return E_INVALIDARG;

      CComQIPtr<IEnumVARIANT> pEnumVariant = pEnum->punkVal;

      ULONG         celtFetched;
      CComVariant   v;

      if ( S_FALSE == pEnumVariant->Next(1, &v, &celtFetched) ) return E_FAIL;
      if ( VT_DISPATCH != v.vt ) CHECKHRESULT(v.ChangeType(VT_DISPATCH));

      return v.Detach(pVar);
   }
   catch(...)
   {
      return E_UNEXPECTED;
   }
}

Then, all I had to do was add the new dll to the installer support files and then in my InstallScript load it up.  Here's my InstallScript that I wrote to enumerate the websites on the current machine.  This is not a complete example as my custom dialog loads up websites and filters virtual directories based on website selection, but this should be enough to whet your appetite.  If you want my full code, just let me know and I'd be happy to share. (Of course I'd expect full credit if you use it in your own stuff ;-)

#define ERROR_SUCCESS   0

prototype loadForEach();
prototype unloadForEach();
prototype ForEach.ForEachStart(byref OBJECT, byref VARIANT);
prototype ForEach.ForEachNext(byref VARIANT, byref OBJECT);
prototype LIST getWebsiteList();

function loadForEach()
   STRING dllPath;
begin
   dllPath = SUPPORTDIR ^ "ForEach.dll";

   if ( 0 != UseDLL(dllPath) ) then
      MessageBox("Failed to load dependency file; cancelling installation.", SEVERE);
      abort;
   endif;
end;

function unloadForEach()
   STRING dllPath;
begin
   dllPath = SUPPORTDIR ^ "ForEach.dll";
   UnUseDLL(dllPath);
end;


function LIST getWebsiteList()
   OBJECT w3Svc, site;
   VARIANT varEnum;
   LIST ret;
begin
   // create the list to be returned
   ret = ListCreate(STRINGLIST);
 
   loadForEach();
   set w3Svc = CoGetObject("IIS://localhost/W3SVC", "");
   if ( IsObject(w3Svc) ) then
      ForEachStart(w3Svc, varEnum);
      while ( ERROR_SUCCESS = ForEachNext(varEnum, site) )
         if ( "IIsWebServer" = site.Class ) then
            ListAddString(ret, site.ServerComment, AFTER);
         endif;
      endwhile;
      set varEnum = NOTHING;
   endif;
   unloadForEach();
   return ret;
end;

All in all I'm pretty pleased with this code, but it did take some time to get used to it; it's kind of a mix between VB and C.

My biggest question is this: How can a tool as powerful and widely used as InstallShield make it to version 10 (I'm assuming that's what the 'X' means) and not have any support for enumeration?  I'm glad they have support for extensibility and native Win32 calls, but c'mon, this is ridiculous!

Wednesday, August 25, 2004 7:19:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback

I tasked myself the other day to generate a list of websites and their contained virtual directories on my local machine.  This data could be used in a variety of circumstances (from installers to administrative tools).  Anyway, this is the .vbs that I came up with as a test harness:

Call DisplayTree(GetObject("IIS://LOCALHOST/W3SVC"), 0, "")

Sub DisplayTree(obj, depth, path)
   If ( "IIsWebServer" = obj.Class ) Then
      WScript.Echo Space(depth * 3) & path & obj.Name & " - " & obj.ServerComment & " (" & obj.Class & ")"
   ElseIf ( "IIsWebVirtualDir" = obj.Class OR "IIsWebService" = obj.Class ) Then
      WScript.Echo Space(depth * 3) & path & obj.Name & " (" & obj.Class & ")"
   End If

   Select Case obj.Class
      Case "IIsWebService", "IIsWebServer", "IIsWebVirtualDir", "IIsWebDirectory"
         Dim currObj
         For Each currObj in obj
            Call DisplayTree(currObj, depth + 1, path + obj.Name + "/")
         Next currObj
   End Select
End Sub

Simply copy the text and paste it into a new .vbs file (e.g. EnumVirtualDir.vbs) and run it from the command prompt: cscript EnumVirtualDir.vbs

Kinda handy!

Wednesday, August 25, 2004 6:51:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

I'm a component developer.  Always have been.  I have a passion for creating reusable units of code - especially in the form of controls.  Back in the day, I was an avid ActiveX control developer and if I say so, some of my controls have been very successful and well received...particularly a dropdown ActiveX calendar control (more on that another day).

ASP.NET developers are (or should be) familiar with two types of controls: UserControl and WebControl.

UserControls are in essence HTML with code-behind that are referenced via a URL on the containing page.  They can be loaded dynamicaly (via Page.LoadControl(”...ascx”)).  A UserControl provides visual HTML reuse.  Perhaps some good examples of when to use a UserControl would be a custom link, a data entry form, or a link/menu bar.  I am probably not alone when I say that I think they're really cool.

WebControls, on the other hand, are quite a bit more complicated - but more powerful as well.  Unlike the UserControl, the WebControl doesn't have a nice, pretty HTML front-end.  All of the rendering of the control happens server-side in the WebControl's Render() method.  To create a WebControl basically all you need to do is derive from WebControl and override the Render() method and output your custom HTML as in the following example:

namespace Devstone.Web.TestControls {

   public sealed class MyWebControl : WebControl {
      protected override void Render(HtmlTextWriter output) {
         output.Write(“Custom control text“);
      }
   }

}

Then in order to use this control on a page you do the following on the .aspx page:

<%@ Register tagPrefix=“devstone“ Namespace=“Devstone.Web.TestControls“ Assembly=“Devstone.Web.TestControls“ %>
<body>
   <devstone:MyWebControl runat=server />
</body>

That's pretty slick...but you can do oh so much more than this.

If your control has properties (as most in fact do), you can reference the controls as child Xml elements to the tag on the aspx page or as attributes.  Personally, I prefer the attributes approach.  However, suppose you want to have a control that has child elements that you want to be child controls - not properties.  The trick is to add the ParseChildrenAttribute to your WebControl with a value of false.  When you do this, each element will be interpreted by a built-in parser and handed off to a control builder class that you designate with the ControlBuilderAttribute.  Upon parsing the element, ASP.NET will call into your WebControl's AddParsedSubObject() method, passing in the new control instance.

A word of caution, however.  The ASP.NET parser is kinda stupid (or maybe it's brilliant and I can't fathom it's logic).  It will call AddParsedSubObject(), passing in LiteralControl instances for each line between the child elements and you probably want to ignore those, only looking for your own custom tags.  Here's the updated control now:

[ParseChildren(false)]
[ControlBuilder(typeof(MyWebControlBuilder))]
public sealed class MyWebControl : WebControl {
   protected override void AddParsedSubObject(object obj) {
      // ignore all child controls except the ones we care about
      MyCompanyLink link = obj as MyCompanyLink;
      if ( null != link )
         this.Controls.Add(link);
   }

   protected override void Render(HtmlTextWriter output) {
      // render all child controls
      // this is kinda brain-dead, but it gets the point across
      foreach ( Control ctl in this.Controls )
         ctl.RenderControl(output);

      output.Write(“Custom control text“);
   }
}

internal sealed class MyWebControlBuilder : ControlBuilder {
   public override Type GetChildControlType(string tagName, IDictionary attributes) {
      switch ( tagName.ToUpper() ) {
         case “COMPANYLINK“:
            return typeof(MyCompanyLink);
         default:
            return null;
      }
   }
}

At this point, all I have to do is define the MyCompanyLink control, derive from a WebControl and override it's Render() method.  The .aspx page might look like this:

<devstone:MyWebControl runat=server>
   <companyLink />
</devstone:MyWebControl>

Granted, this is pretty simplistic.  What if we're creating a control that has more than one level of nested children controls?  An example of this might be a menu or a toolbar control.  Well, what we've talked about so far applies, but it's not enough.  By default the ASP.NET parser will only parse down one level for child controls.  If you go to the trouble of creating a child control such as a toolbar and add buttons, the buttons will be interpreted as properties of the toolbar.  Ok, so you can add the ParseChildrenAttribute to the child control, but it's ControlBuilder will never be invoked.

After a bit of digging I found the solution and it's pretty simple:  Add the PersistChildrenAttribute to the root object.

[ParseChildren(false)]
[PersistChildren(true)]
[ControlBuilder(typeof(MyWebControlBuilder))]
public sealed class MyWebControl : WebControl { ... }

Now, if you had, say a toolbar child control you could do the following to create a custom button arrangement:

<devstone:MyWebControl runat=server>
   <toolBar>
      <cut />
      <copy />
      <paste />
   </toolBar>
</devstone:MyWebControl>

I am, in fact, in the process of creating a control that does just this and when it's ready I'll post it up here for public consumption.  Enjoy!

Wednesday, August 25, 2004 6:32:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Tuesday, August 24, 2004

Every developer needs his (or her) bag of tricks, techniques, and strategies.  This is a trick that I've used for ages, but today I really needed it so I thought I'd share my thoughts.

If you've ever had the opportunity (or [dis]pleasure) of having to debug Javascript in Visual Studio you've probably had your share of frustrations.  Sure, there are documents out there that identify all of the settings you need to make to ensure proper debugging of client-side script, but I've not found them 100% reliable.  Sometimes, you can establish breakpoints in your code and step through it in a debugging session, while other times you cannot.  At this time I'm not going to go through all the nuances of the debugger, but instead point out a cool set of tips.

The one technique that I've never seen fail is the use of the debugger statement in your client-side Javascript.  The first thing you have to do is enable client-side debugging by unchecking the 'Disable script debugging' option in Internet Explorer.  Next, add the debugger; statement anywhere within your Javascript code.

If you run your ASP.NET application from within VS.NET, the debugger; statement will act as a breakpoint.  Otherwise, when IE hits the debugger; statement it will prompt you for a debugger to use (preferrably VS.NET) when the statement is hit at runtime.

Very cool!

Tuesday, August 24, 2004 12:46:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, August 18, 2004

Perhaps for my own benefit, I post the following.  Writing down (or typing) something usually helps me remember things, so hopefully this will serve as a reminder going forward.  I do this once in a blue moon, and everytime I have to go hunt it down and then for some oddball reason, I promptly forget it, promising myself I won't.

Every once in a while, Internet Explorer (6.0 in my case) seems to forget to display controls (such as buttons and dropdowns) using the current Windows theme - that is, with the nice sleek edges, rounded corners, and mouseover behavior.  I haven't nailed down what it is that is causing this behavior; I don't have any CSS styles that would otherwise alter the border-style of the elements or anything.  However, there is a META tag that we can use to force the issue.

By simply adding the following tag to the <head> section of the page you can ensure that themes will be (or will NOT be) supported on the page:

<meta http-equiv=”MSThemeCompatible" content="yes">

Wednesday, August 18, 2004 5:02:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, August 14, 2004

Talk about frustrating!  Over the past several weeks I've been working on a web application that exhibited very interesting behaviors.

After compiling it, I would then browse to my virtual directory and the very first time it ran I would get errors reported.  Then I would refresh the page and it would work - for a bit.  Then I would get the same errors reported.  I'd refresh a second time and from that point on it ran flawlessly.  Interestingly, these problems did not exist on a coworker's machines.  However, if I ran the application from Visual Studio .NET, it ran fine from the get go.

Needless to say, I started to dive it and try to figure it out.  I robustified my error logging mechanisms in my application so that more descriptive messages would be written to the event log.  In short order, this led me to see that when from IE my ASP.NET Session and Application were recycling.  This is why I was getting the errors...I was loading objects up into Session at the start and then, all of the sudden, the values were gone.  I found it interesting that when run from VS.NET, the session objects would remain even after the Session recycled!

I did a little digging and found a couple of articles (article1, article2) on support.microsoft.com that offered some hope.  Well, after lots (and lots) of experimentation, I found that the articles offered the correct solution.  My virus scanner was affecting the session.  After I performed a rebuild (and a subsequent deploy), the OS marked those files as modified which recycled the process (ok, I was expecting that one).  Then, the anti-virus software would scan the files, affecting them again, again recycling the process.  The solution offered by the MS articles (and others) indicated to turn off AV scans on my \WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files folder.  That didn't seem to fix the problem.  However, I turned off scanning on my \Dev folder and all was hunky dory!

Sometimes, what seem to be the simplest of issues...are!  Once you know what the solution is.  For the longest time I thought it was a threading issue, but my robustified error logging eliminated that possibility :)

Saturday, August 14, 2004 11:52:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback