Monday, December 27, 2004

I was in the process of writing some code that extended the functionality of the OnKeyPress() method of a TextBox (RichTextBox to be precise).  This code has the need not only to validate text as it is entered, but also to perform certain actions based on whether certain keystrokes are made within it.  The OnKeyPress() method receives as it's parameter a KeyPressEventArgs object indicating the key that was pressed.  It does not, however, contain any information as to whether the CTRL, SHIFT, or ALT keys are pressed - that information is reserved for the OnKeyDown() and OnKeyUp() methods.

Being the in-a-past-life-Win32 developer that I am, my first inclination was to write the following code:

[DllImport(“user32.dll“)]
private static extern Int16 GetKeyState(Int32 nVirtKey);
private const int VK_CONTROL = 0x11;

And then within the OnKeyPress() method:

bool ctrl = ( ( GetKeyState(VK_CONTROL) & 0x8000 ) > 0 );
if ( ctrl ) {
   //...
}

This, however, seemed too un-.NET-ish.  Therefore, I started to investigate.  It didn't take long however, and I found what I was looking for.  It turns out the Control class exposes a static method called ModifierKeys that indicates the state of the CTRL, SHIFT, and ALT keys.  My code suddenly became much cleaner and w/o the need to use P/Invoke to get the data I wanted:

if ( Keys.Control == Control.ModifierKeys ) {
   //...
}

Monday, December 27, 2004 9:47:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, December 17, 2004

In my previous life (pre-.NET), I devoted a lot of time to being a component/control developer and therefore became intimately familiar with the GDI and the WinAPI in general.  One of the critical functions to be aware of is the DrawEdge GDI function.  This method provides the mechanism that the OS uses to render the three-dimensional edges for controls in the system (buttons, textboxes, frames, etc).

When you make the transition to .NET, however, the DrawEdge function is nowhere to be found.  Sure, you can DllImport the thing and call it natively via P/Invoke, but I want native, framework support.  As it turns out, you can call the DrawEdge function with built-in framework support, but it's not where you might expect it.

Within the System.Drawing namespace there is a class called ControlPaint that consists entirely of static methods.  These methods enable the developer to render your typical UI features such as buttons, checkboxes, grab handles, focus rectangles, etc.  It's nice that all of these functions are placed in one easy-to-get-to place.  The replacement(s) for the DrawEdge function are DrawBorder and DrawBorder3D.  At first I wondered as to why there was no DrawEdge function on the Graphics object.  It would be so convenient to have code like e.Graphics.DrawEdge(...) in the OnPaint(), but then I got to thinking about it and it makes so much more sense to have UI-related rendering methods consolidated into their own class.

I'm in the process of creating a toolbox control not dissimilar to that within VS.NET, but not as fully featured either.  This control needs the ability to dynamically have controls (labels) added to it and I wanted to track mouse events on each label, displaying a slightly 'popped-up' edge.  Here's a real quick class that I created (I literally wrote it in about 30 seconds) that accomplishes this with minimal code.  Sure, I'll flesh it out a bit more to get exactly the behavior I want, but it's just roughed out so far.

internal class LabelTool : Label {
   private bool _mouseOver = false;

   protected override void OnMouseEnter(EventArgs e) {
      base.OnMouseEnter(e);
      _mouseOver = true;
      this.Invalidate();
   }

   protected override void OnMouseLeave(EventArgs e) {
      base.OnMouseLeave(e);
      _mouseOver = false;
      this.Invalidate();
   }

   protected override void OnPaint(PaintEventArgs e) {
      base.OnPaint(e);
      if ( _mouseOver ) ControlPaint.DrawBorder3D(e.Graphics, this.ClientRectangle, Border3DStyle.RaisedInner);
   }
}

Friday, December 17, 2004 7:00:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, December 15, 2004

Here's a cool little quiz that a friend shared with me.  Test your knowledge of the geography of the United States of America.

First (and only attempt): Score 94%, Avg Error: 9 miles, Time: 563 seconds.

Update 07/26/2005:  On a whim, I thought I'd try it again.  Including one accidental slip of the mouse, my new results:
Score: 96%, Avg Error: 5 miles, Time: 383 seconds

Wednesday, December 15, 2004 11:53:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, December 13, 2004

Call me late to the party.  I know there was a lot of hype about the show when it aired a few years ago, but I literally watch less that 30 minutes of TV a week (besides a few select sporting events involving the University of Utah Utes Football team and a few other choice selections).  A good friend of mine, lent me Season 1 and Season 2 of “24”.  Oh my goodness I am hooked!  I've had the opportunity to watch about 4 episodes a night (1 dvd/night) and I'm dying to see the next four episodes.  So far I'm half way through the first season, having arrived at 12:00 pm and I'm anxiously awaiting tomorrow night when I can find out what happens next.

In all honesty, I'm kinda glad I've waited until now to see them, after the hype has passed so I can relive the excitement again - without any commercials and having the cliff-hanger that lasts a week.  Now the cliff-hanger lasts about 20 seconds (or at most one day).

Well, I just wanted to share my 'discovery'.  I am delighted to watch such rivetting, high-quality programming.  I haven't had this much fun following a program since the X-Files.

Monday, December 13, 2004 6:27:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Friday, December 10, 2004

A good friend of mine, Scott Golightly, is now blogging!  Employed at Keane, Inc, Scott has been a consultant for over 12 years.  As an alter-ego, Scott parades around as our local Microsoft Regional Director, giving the occasional talk at the .NET User Group or even speaking at Tech Ed.

Glad you joined the blog party!  Welcome aboard!

Friday, December 10, 2004 5:10:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Just last night I had need to create a server-side repeater control programmatically.  This is something that, thus far, I have not had much need to do.  Most of the time, I've resorted to embedding <asp:repeater /> controls (or some other control such as a DataList or a DataGrid) on an .aspx or .ascx control and establishing templates as in the following:

<ul>
<asp:repeater id=repeater runat=server>
   <itemtemplate>
   <li><%# DataBinder.Eval(Container.DataItem, "DataField")%></li>
   </itemtemplate>
</asp:repeater>
</ul>

This, I believe, is a pretty popular route that developers take.  My circumstances, however, proved to be different this time.  I needed to create a server control (derived from WebControl) that utilized a templated Repeater control.  The trick here wasn't so much getting a Repeater control on the page, but rather to create a data-bindable template without the 'code-in-front'.

This was, in fact, extremely easy to accomplish despite all of the useless help examples in MSDN.  In fact the MSDN help only added hot coals to the fire of confusion so I abandoned it to figure it out manually.

Basically, what it came down to was that I had to create a class that derived from ITemplate and assign an instance of this class to the ItemTemplate, AlternatingItemTemplate, or whatever property of the Repeater control.  By implementing the ITemplate.InstantiateIn() method, we provide the runtime the opportunity to hook our template into the proper parent control.  In order to render the output html we have to use server controls (such as the Literal control) because we cannot add plain text directly to the Controls collection of the parent control.

That's all there is to it to create a simple template, but for the most part that isn't enough.  One of the most powerful and luring capabilities of templates is their ability to data bind.  In this case we cannot simply embed the databinding tags <%#...%> within our text because those expressions are parsed out of an .aspx/.ascx and turned to code before the template is ever created.  Therefore, in order to accomplish this, we must attach to the DataBinding event of the control outputting the data (the Literal control in our case).  This is where things get a bit more involved - but not by much.

The DataBinding event has the signature of a typical EventHandler (object sender, EventArgs e).  The sender parameter can be cast to the Literal control as it is the control on which the event is firing.  Then, to get at the data that is being bound to our template instance we must acquire a reference to the RepeaterItem (because we're dealing with a Repeater control).  The RepeaterItem has a property called DataItem from which we can extract the data being bound.  After that, it's merely a matter of dumping the data to the control.

Suppose that we had the following Customer class:

public class Customer {
   private string _id;
   private string _name;
  
   public Customer(string id, string name) {
      _id = id;
      _name = name;
   }
  
   public string Id {
      get { return _id; }
      set { _id = value; }
   }
  
   public string Name {
      get { return _name; }
      set { _name = value; }
   }
}

Now an array of Customer instances will be provided to the fictitious control for data binding.  Our control might resemble the following in the server-side code:

public class CustomerListing : WebControl {
   private Repeater repeater;
  
   protected override void OnInit(EventArgs e) {
      base.OnInit(e);
     
      // create the repeater
      repeater = new Repeater();
      this.Controls.Add(repeater);
     
      // assign the template instance to the repeater
      repeater.ItemTemplate = new CustomerTemplate();
   }
  
  
   protected override void OnLoad(EventArgs e) {
      base.OnLoad(e);
     
      Customer[] customers = BusinessLayerSomethingOrOther.GetCustomers();
      repeater.DataSource = customers;
      repeater.DataBind();
   }
  
  
   protected override void Render(HtmlTextWriter writer) {
      writer.Write("<b>Customer List</b>");
      writer.Write("<ul>");
      base.Render(writer);
      writer.Write("</ul>");
   }
}

Now it's time to get into the template.

internal class CustomerTemplate : ITemplate {
   Literal lit;
  
   void ITemplate.InstantiateIn(Control container) {
      lit = new Literal();
      lit.DataBinding += new EventHandler(dataBind);
      container.Controls.Add(lit);
   }
  
  
   private void dataBind(object sender, EventArgs e) {
      // get a reference to the Literal control
      Literal lit = sender as Literal;
      // get a reference to the templated item
      RepeaterItem container = lit.NamingContainer as RepeaterItem;
      // note: we could use DataBinder.Eval(...) but I'd rather not take the perf hit
      Customer cust = ((Customer)container.DataItem);
      lit.Text = string.Format("<li>{0} - {1}</li>", cust.Id, cust.Name);
   }
}

As you can see, it's pretty easy to accomplish this!  There is so much more than can be accomplished, but this is merely a first step into the greater world of server-side templating.

Friday, December 10, 2004 4:16:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [14]  |  Trackback

Boy!  This has been a slow month for blogging!  I've been travelling so much and working so hard that once again my blog has become secondary.

I am out of town at the moment and regrettably missed our .NET User Group meeting last night.  My good friend and associate, Matt Smith, from our local Salt Lake City Microsoft offices gave what I'm sure was a excellent talk on Sql Server Reporting Services - Matt never fails to impress me on any level.  As perhaps one of the best developers I have ever met, I'm always impressed.

In my experience, I routinely run into developers that have never even heard of SRS which is regrettable.  I hope that the presentation was not only an eye-opener but replete with technical goodies that developers crave.

Friday, December 10, 2004 3:28:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, December 02, 2004

This hit me pretty good today - it took about an hour and a half to solve.  The whole time I spent almost pulling my hair out...though I did have a glass of apple juice which whet the appetite nicely.

Ever since I removed myself from my local Administrators group, I've been wanting to test a particular website that I developed (and have been developing for some time).  The website uses impersonation, thereby allowing me to effectively run as a different user.  I didn't want, even for testing/development purposes, to have direct access to databases or other objects.  Rather I've wanted to write the software in an environment more like the one into which it will be deployed.

Well, because I removed myself from the Administrators group, I have had to run ASP.NET as a user other than ASPNET in order to debug the websites.  Of course, this was not a problem at all.  I simply encrypted my credentials via aspnet_setreg.exe and updated the machine.config file's to point to the registry as the source for its credentials.  All has been great - until today, well, yesterday, but that's a different story all together.

Upon attempting to run this website, I was greeted with the not-so-friendly-but-somewhat-informative message of “Access Denied to 'C:\...\Index.aspx'.  Failed to start monitoring file changes.” Therefore, I attempted to run the project from within Visual Studio, to see if I could further debug the problem and isolate any issues.  I got a different message all together: “Configuration Error.  Unable to load c:\windows\...\machine.config.  Either a required impersonation level was not provided, or the provided impersonation level is invalid.”

This seemed a simple, permissions settings.  I proceeded, therefore, to grant my impersonated user rights to the folders and files in question.  No dice.  Same message.  Hmmm, this was getting interesting - and a tad frustrating.

I tried granting the user Full Control at the root C:\ level to see if that took care of things - a big fat nope!

Interestingly, if I set the impersonated user to be my domain administrator it still didn't work.  It ONLY worked under my account.  Then I started to think - I probably don't have rights to impersonate.  When I was in the Administrators group I had that right by membership, but now that I'm a lowly User, that privilege had been stripped.

The solution?  Well, I opened my Local Security Policy (running it as the administrator), expanded Local Policies\User Rights Assignment and navigated to the 'Impersonate a client after authentication' policy setting.  Sure enough only three accounts by default were granted this right:  Administrators, ASPNET, and SERVICE.  I simply had to add my account (because that's the account the ASP worker process is running as) to the list.  Then I opened the command prompt and ran gpupdate /force to ensure that all policy changes took effect, then ran iisreset.

Now it works and I'm ready to ride off into the sunset and retire - at least for the evening.

Thursday, December 02, 2004 6:12:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [14]  |  Trackback
 Wednesday, December 01, 2004

It just goes to show you!  When you feel you know and understand a product pretty well, some new nook or cranny appears, allowing you into previously unexplored territories.

I never had explored this facet of IE, but Nikhil Kothari identifies that you can create your own stylesheets and apply them to Internet Explorer in general.  Specifically, he points out simply enlarging (zooming) the contents of the browser by creating a simple stylesheet containing nothing but body { zoom: xxx% }.  This is fantastic - especially for presentations!

One thing of note, because I had never looked into the zoom attribute before:  not only does it enlarge what the browser is presenting (text, images), but also its scrollbars.  Likewise, it does not zoom contents for which HTML (for one reason or another in IE) has no control over, such as your caret or combo boxes.

Additionally, this stylesheet, should you choose to apply it, affects ALL Internet Explorer-based applications on your system universally (Outlook, Web Browser controls, etc), so choose your styles with care.

Wednesday, December 01, 2004 2:09:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, November 30, 2004

As I had mentioned previously, I spent some time recently creating a control that would provide a facility to allow users to rate blog content (posts, comments, stories, pictures, you name it) and provide their own two cents.  This control is presently implemented on my own blog as you can see.  I’m making a few late breaking changes and creating the documentation.  When completed, I will post it here for use on your own .Text blog if you’d like.  If you simply MUST have it sooner, contact me and we’ll get it set up.

Getting the control implemented on a .Text site wasn’t extremely easy, but it wasn’t difficult either…you just have to know what to do.  If you recall, the entire control (and its associated pages) is encapsulated within a single assembly: Devstone.Web.Blog.Controls.Rating.dll.  This enables you to deploy the control to your blog by simply dropping the .dll in the \bin folder, tweaking the web.config, and running a database script (which simply adds a table and a few stored procs).

I have not tested this with .Text versions other than 0.95.2004.102.  Also, my blogs have been the multi-user/community blogs rather than a single-user blog, but I imagine that getting it setup on a single-user blog would be pretty painless and follow much of the same procedure.

And now for the nitty gritty...

There are two components to the rating control:  a Rating WebControl and a Rating Page.  In order for the server to process requests I decided to handle the requests using an HttpHandler (technically, an HttpHandlerFactory and an HttpHandler for the dynamic images).

Within IIS I mapped the .dspx extension to the aspnet_isapi.dll, checking to not enforce that the file exist (because it doesn’t exist in this case) and allowing the verbs GET and POST.  The .dspx extension is of my own creating (meaning Devstone Server Page).

Then I updated the web.config file.  Some care had to be made here.  .Text usurps ASP.NET in how requests are handled by defining its own suite of HttpHandlers depending on how the client request comes in.  Therefore, I could not add my handler to the <httpHandlers /> section as one would traditionally do – I tried and got erratic results; sometimes it worked and other times it didn’t.  Instead, I had to add my own <HttpHandler /> to the HandlerConfiguration/HttpHandlers node.

In fact this is the trick to making the whole thing work.  You just have to make sure the regular expression is valid.  And because I’m using a ‘Factory’ rather than ‘Handler’ to handle the requests my statements ended up looking more or less like this:

<HttpHandler pattern="^(?:/(\w|\s|\.)+/img/\d\.\d{2}/(?:Star|Dot|Bar)\.dspx)$" type="Devstone.Web.Blog.Controls.RatingHandlerFactory, Devstone.Web.Blog.Controls.Rating" handlerType="Factory" />

<HttpHandler pattern="^(?:/(\w|\s|\.)+/rate/(?:Star|Dot|Bar)/[12345]/\d+\.dspx)$" type="Devstone.Web.Blog.Controls.RatingHandlerFactory, Devstone.Web.Blog.Controls.Rating" handlerType="Factory" />


If I had created HttpHandlers, rather than HttpHandlerFactories, I would have specified a handlerType of ‘Direct’.  Also, of note:  depending on how your blog is set up, the regular expression will differ. 
http://blog.devstone.com/ for example, is at the root so my regex starts ^(?:/(\w|\s|\.)+/….  If the blog is rooted in a virtual directory, your regex may resemble ^(?:/\w+\/(\w|\s|\.)+/…

Additionally, my control relies on a few custom configuration values that I added to the <appSettings /> section for ease – though I could have just as easily created my own configuration section.

Once those steps were accomplished, all I had to do was add the <%@ Register %> directive to each of the .ascx controls on which the control would be rendered.  For example, I wanted the control to sit along side each post on the blog’s main page.  Therefore, I opened the Day.ascx control and added the following lines:

<%@ Register tagPrefix="devstone" namespace="Devstone.Web.Blog.Controls" assembly="Devstone.Web.Blog.Controls.Rating" %>
<%@ Import Namespace="Dottext.Framework" %>
<%@ Import Namespace="Dottext.Framework.Components" %>


Then navigated down to within the <ItemTemplate /> element of the DayList Repeater control and added the following line of code:

<devstone:ratingsummary id=rs runat=server alignment=right postId="<%# ((Entry)Container.DataItem).EntryID %>" postLink="<%# ((Entry)Container.DataItem).Link %>" />


That’s all there is to it.  This associates the proper id and url with the rating control.  The link isn’t strictly necessary, but is included as a courtesy for when the post is rated, the owner of the blog is sent an email with a link to the post within it.

There are a slew of other properties that can be assigned which I will discuss in the next post when I provide the final documentation and product for download.

Tuesday, November 30, 2004 1:35:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [9]  |  Trackback