Translate DOM Element In WebView To View Coordinates

I set out to figure out how to translate a DOM element in the WebView to a rectangle on the screen. I wanted to display visual cues when clicking on the web page that highlight the DOM element that was clicked.

After about a week of searching, it turns out the answer is pretty simple. The WebView provides a method called elementAtPoint() which takes an NSPoint as it’s parameter. You simply need to intercept a mouse click event and obtain its locationInWindow and pass that to elementAtPoint which will return you an NSDictionary containing several objects. The object you’re interested in can be obtained by calling objectForKey on the NSDictionary passing it the key @”WebElementDOMNode”. Check to see that the object exists and then you can use it. I simply set the returned object to a DOMNode object which contains within it the magic variable called boundingBox. You now have your rectangle and know exactly where on the view the DOM element you clicked is being displayed. Here is some code to demonstrate:

// Inside your mouse click event handler
NSPoint point = [theEvent locationInWindow];
NSDictionary *dict = [webView elementAtPoint:point];
			
DOMNode *node = [dict objectForKey:@"WebElementDOMNode"];

// Make sure the node is not nil
if( node )
{
	NSRect rect = [node boundingBox];
	int left = rect.origin.x;
	int top = rect.origin.y;
	int width = rect.size.width;
	int height = rect.size.height;

	// Do something with the coordinates
	// ...
}

At that point you can do what you would like. I want to draw an overlay rectangle to highlight the DOM element on the screen, but from what I understand, drawing over the WebView may be a bit of a challenge. I’ve experimented with just adding another div with absolute positioning to the DOMDocument to act as my rectangle overlay, but I’ve had some issues with it. I’ll keep you posted here on my progress.

Also, you should probably know that in order to intercept a mouse click when you have a WebView in your window, you’ll have to subclass NSWindow and override - (void)sendEvent:(NSEvent *)theEvent and filter for left mouse clicks. Let me know if you need a further explanation.

XCode 3.0 Tutorial


I’ve seen numerous posts on different discussion boards posted by new Mac users wanting to learn to write code for the Mac. The main complaint is that Interface Builder is too different from the existing documentation, including Aaron Hillegass’s excellent Cocoa Programming for Mac OS X (which actually remains very relevant, by the way. You can wait for some updated books to come out, but Aaron’s book still provides a lot of concepts and even methods that apply. You just need to know what’s changed in XCode 3 and Objective-C 2). So, I’m going to do a quick tutorial here. FULL DISCLOSURE: I learned this from Marcus Zarra, so credit where credit is due. ;-)

Here are some points that you should keep in mind before moving ahead:

  • Don’t use Interface Builder to generate code. Create your code in XCode and let Interface Builder work with it. This isn’t absolutely necessary, but as Marcus told me, it’s just easier to get in this habit as the XCode 2 method of creating your controller, instantiating it, and generating your code files from Interface Builder simply isn’t available in the same way in XCode 3.0. I’ve come to see what Marcus is saying. It makes sense, but you’ll have to get into the process yourself to fully understand it. We’ll get to that in a minute.
  • Understanding Cocoa programming is much simpler if you learn MVC. You can probably step through code examples and figure some things out without learning MVC (Model, View, Controller), but I wouldn’t recommend it. Go Google it and read up on it.

    I will say as an introduction to it for those who are not familiar that it should probably be called (Model <--> Controller <--> View) or (View <--> Controller <--> Model) as the controller always sits between the other two. Your controller is either telling your model to update its data or it is telling the view to update its display. That’s the crux of the whole paradigm. The details run much deeper, but that’s how I would nutshell it for you.

Create Your Application

Let’s get started. Create a Cocoa Application using the following steps:

  1. Select File > New Project… and choose Cocoa Application in the ensuing dialog. Click Next
  2. Enter ‘FirstApp’ as the project name. Click Finish

You should see a project workspace like the following:
FirstApp Workspace

The next thing you should do is create a class to act as your controller or delegate.

Delegate == Controller

The words delegate and controller can be used synonymously. You’ll see later that we delegate the work of the different controls we create in Interface Builder to a delegate or controller class. This should all make perfect sense momentarily. First, create your controller/delegate class using the following steps:

  1. Option-Click on the Classes folder in your workspace and select ‘Add File…’
    Add File
  2. Choose Object-C Class in the ensuing dialog and click Next
    New File Dialog
  3. Name the file ‘AppDelegate.m’ and click Finish
    New Objectiv-C Class
  4. A new code window will display with your ‘AppDelegate’ interface code (.h file). Add an outlet for a text field and a label, and an action to the code so that it looks like this:
    @interface AppDelegate : NSObject {
    	IBOutlet NSTextField *textField;
    	IBOutlet NSTextField *label;
    }
    - (IBAction)clickButton:(id)sender;
    @end
    
  5. Now switch over to your implementation file for ‘AppDelegate’ (.m file). Add the clickButton implementation code so that the file looks like this:
    @implementation AppDelegate
    
    - (void)clickButton:(id)sender;
    {
    	
    }
    
    @end
    

    We will actually add some code to do something in the clickButton handler, but first we need to hook it up to the user interface in Interface Builder.

Interface Builder And Controller/Delegate Implementation

Now that you’ve specified the outlets–two NSTextFields, one a text field and one a label in this case–and an action called clickButton, you will see these items available for connecting to the UI in Interface builder. Let’s open Interface Builder and make the connections we need using the following steps:

  1. In your XCode workspace, expand the folder in the tree view called NIB Files and double click the file called ‘MainMenu.nib’. This will open the nib file in Interface Builder
    Select NIB File in Workspace
  2. Once Interface Builder loads select the NSObject item and drag it into the ‘MainMenu.nib’ window.
    Add NSObject
    Then rename it to ‘AppDelegate’
    Interface Builder AppDelegate

Making Connections (Pay Attention Here)

These next two steps are critical for your understanding. You now need to tell Interface Builder the following:

  • What object you want to use as a File’s Owner. You will delegate actions to a delegate/controller object. You tell the application which object to use by setting the File’s Owner delegate.
  • What type of object this delegate is. Before you can specify which object to use as the File’s Owner you have to set the object type. A regular NSObject doesn’t provide any implementation for your app. You need to tell Interface Builder that you want your NSOBject to actually be an object of type ‘AppDelegate’.

You can achieve this by completing the following steps:

  1. If you haven’t already, open the Inspector in Interface Builder by selecting Tools > Inspector
  2. Click on your NSObject that you named ‘AppDelegate’ and then click on the Identity tab in the inspector.
  3. Change the class to type ‘AppDelegate’ which will be available in Interface Builder as it has been able to obtain the class information from XCode.
    Set Delegate Class

    You should also notice at this point in the Class Actions and Class Outlets sections of the Inspector, your action, clickButton: and your outlet, the NSTextField are now visible. We’ll hook those up in just a minute.

  4. Control-Click the File’s Owner object in the ‘MainMenu.nib’ window and drag it to the ‘AppDelegate’ object.
    Set File's Owner

    A pop-up list will display. Select delegate.
    Set File's Owner Delegate

Design The User Interface

Now you simply need to add the controls to the main window in Interface Builder and then we can connect the action and outlet accordingly. To finish the interface, complete the following steps:

  1. Drag a TextField, a Label, and a Button to the main window so that the user interface looks like the screenshot below:
    Application Main Window Design
  2. Control-Click and drag from the Buttonto your ‘AppDelegate’ object in the ‘MainMenu.nib’ window.
    Set Button Delegate
    A pop-up will display. Select clickButton:
    Set Button Action
  3. Control-Click the ‘AppDelegate’ object and drag it to the text field in the main form.
    Connect Text Field
    A pop-up will display. Select textField
    Select Text Field
  4. Control-Click the ‘AppDelegate’ object and drag it to the label in the main form.
    Connect Label

    A pop-up will display. Select label
    Select Label

That’s it for Interface Builder. You can quit interface builder and return to XCode. We have one more piece of code to add and then our application will be finished.

Finishing Up

When the button is clicked, it will simply grab the text from the text field and place it into the label. That’s all the application does. Here’s the code you need. Just make your implementation in the ‘AppDelegate.m’ file look like this:

@implementation AppDelegate

- (void)clickButton:(id)sender;
{
	NSString *text = [textField stringValue];
	[label setStringValue:text];
}

@end

Now all you need to do is click “Build and Go”. When the application runs, type some text into the text field and click the button. You will see the label update with the text from the text field.

When I originally tried to build this application, I had to get Marcus’ help. Then he showed me a great way to have the label update in real time as you type text into the text field. It was accomplished using the new @synthesize method in Objective-C 2.0. I won’t go into the details of that now, but needless to say there are a lot of different ways to achieve what you want for your app.

Further Discussion

It has been most helpful for me while learning to write code for the Mac that from an interface standpoint, there are two entities that you need to be concerned with–actions and outlets. Outlets are normally UI controls such as text fields, list boxes, and buttons while actions are, well, actions–the action you want to happen when some event from a control is triggered.

If you connect all of your outlets to their declarations in your controller/delegate code implementation, you don’t have to do any control object instantiation in code. There may be times when this is desirable, however, most of the time you can just connect and go. Just use your controls as all of the instantiation/initialization is handled for you by the framework.


You can download the xCode project here: FirstApp Demo Application



Black Boxes and Core Data

Is Core Data a black box?

Reading this post from Theocacao this morning got me thinking about using Core Data in my first Mac app that I’m currently working on.

Five years working with .NET has taught me some valuable yet tough lessons–the black box approach to writing applications can seem like a perfect shortcut, but can quickly turn into the bane of one’s software development existence.

When I use the term black box, I am referring to employing a methodology (usually with data access) that is provided by the platform vendor (namely Microsoft, Apple, etc.) that are there to make the developer’s job faster and easier.

In theory, black boxes do what they are intended to do. In fact any and all of the examples and tutorial videos you might find on the Microsoft website show how to use these tools and they make it look very quick and easy.

I’ve found this to be true in the Core Data examples I’ve seen as well. What’s interesting is the fact that the data sets the examples work with are always very small. This matters a great deal and here’s why.

In the Microsoft world, you can build data models using a model designer that maintains XML code behind the scenes for you. You simply specify the query you want to use to obtain the data through a TableAdapter and then you are able to connect the model to your application without writing a stitch of code. It is “all done for you”. The problem is that it doesn’t scale well. After you’ve accumulated a large number of objects in the designer, the first thing you notice is that the interface becomes sluggish. It’s still useable, however, I know I’ve got a lot more that I need to add to the model before the app is going to be finished. It could get pretty ugly.

What’s worse and more critical to the application, however, is the unknown problems that the black box happily obscures from the developer’s view. I had developed my application using this black box model in Microsoft and had to change the database to a new empty database with the *exact same schema* and when I went to re-run the application, it just stopped working altogether. I could no longer add records to the database using the interface I had developed.

As I looked around the Internet, I found this to be a fairly common problem. I found myself having to frantically and immediately break out of the black box and return to square one–writing my own data access code–to get the app working again. It was a nightmare that taught me that I need to avoid the black boxes.

So, here I am now embarking on developing my Mac programming skills and the first thing I’m faced with is whether or not I should go the black box route that Core Data seems to be.

I say seems because I am new, so please don’t flame me over that comment. If it is not a black box methodology, simply explain to me how that is so. Meanwhile, I’m going to assume that it actually is a black box method and step into it very cautiously. I like Core Data quite well and it really seems to be pretty powerful. However, I can’t help but wonder about scalability and running into the same issues I had using Microsoft’s dev tools. I’ll continue to document my progress and findings here.

Is CamelBones Viable? An Outsider’s Perspective

CamelBones is a Perl-Cocoa/Objective-C bridge framework written by Sherm Pendley and it appears that is doesn’t yet work with OS X Leopard. I’ve written Sherm a couple of messages myself to find out if he has plans to update it for Leopard. I haven’t heard back from him yet, but according to some posts I read from back in May, Sherm may no longer want to pursue this project, or he just can’t afford to since he has been doing it on a volunteer basis. This is a shame to people like me who are just getting into the Cocoa/Objective-C game and would love to have access to Perl inside of my apps–yep, that’s right I want to access Perl from Objective-C.

I realize that you can do this using the PerlObjCBridge, but Sherm’s library is much more robust and, frankly, easier to use. In fact, just before I upgraded to Leopard I got version 1.0.3 of CamelBones working from within a Cocoa application in Tiger. I was stoked. I can now have all of the text parsing power of Perl from within my application and this, from my point of view is the real strength of CamelBones. You can imagine my disappointment when my application would no longer work when built in Leopard.

I suppose that’s the point of my post. Obviously I’m speaking without any evidence whatsoever that the majority of people who might use CamelBones would agree with me, but I really see CamelBones being more viable as an embedded Perl interpreter than anything.

If the examples that are included in the CamelBones distro are any indication, then the majority of people who use CamelBones are using it to be able to develop Cocoa apps from Perl. I submit that because Sherm has had trouble establishing viability using CamelBones in this way, maybe he ought to consider promoting it more from the perspective that those of us who still use and love Perl, would find it far more useful as a way to run perl scripts from within our Cocoa/Objective-C apps. Here is how I am planning to use it:

  • To develop a plugin framework. New plugins developed as perl scripts could use known tokens to access the internals of my app and could therefore allow the app to evolve depending upon what I or others envision.
  • To embed a powerful text parsing engine. Perl is best known for its ability to parse and process text. There are regular expression libraries available for Objective-C, however, to simply embed a Perl interpreter in your app provides a powerful regex engine that is already familiar if you know Perl compatible regular expression syntax.

If need be, I’ll find another way to get what I need in my applications (regex, plugin framework, etc.), however, Sherm’s CamelBones library is mature, solid, and very powerful. It would be ideal. I hope Sherm will continue to work on the project. Meanwhile, if you, like me, use Perl on a regular basis, let Sherm know that you would like to see the project continue–maybe make a donation (Click Donate) to help support its further development.

iCal Development Sample SimpleCalendar Doesn’t Work

I was wanting to learn the iCal API for the Mac so I downloaded the sample application called SimpleCalendar at the Apple Developer Connection website (you’ll need to log in with your ADC member account info). Apparently, when the sample code was last modified it was with code that didn’t make it into the release build of Leopard. The application will compile in xCode 3.0, but it won’t run properly (the calendar never displays).

To fix it, all you need to do is fix all of the warnings that come up when you do a compile. I’ve gone to the trouble of finding the correct API, so I figured I would highlight it here. The FileMerge results between my fixed version of Calendar.m (the offending file) and the original version show that there are four changes. They are as follows:

Line No. Original Fixed
74 NSPredicate *eventsForThisYear = [NSPredicate eventPredicateWithStartDate:startDate endDate:endDate
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSPredicate *eventsForThisYear = [CalCalendarStore eventPredicateWithStartDate:startDate endDate:endDate
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
193 NSPredicate *uidPredicate = [NSPredicate eventPredicateWithStartDate:startDate endDate:endDate UID:uid
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSPredicate *uidPredicate = [CalCalendarStore eventPredicateWithStartDate:startDate endDate:endDate UID:uid
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
240 NSPredicate *uidPredicate = [NSPredicate eventPredicateWithStartDate:startDate endDate:endDate UID:uid
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSPredicate *uidPredicate = [CalCalendarStore eventPredicateWithStartDate:startDate endDate:endDate UID:uid
calendars:[[CalCalendarStore defaultCalendarStore] calendars]];
280 [[CalCalendarStore defaultCalendarStore] saveEvent:object span:CalSpanThisEvent]; [[CalCalendarStore defaultCalendarStore] saveEvent:object span:CalSpanThisEvent error:nil];

I’ve successfully built the application without any warnings or errors and it now will run. On to learning what the code is actually doing.

The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store.

I’ve put the whole error message in the title of this post so that people can find the answer to this problem easier when looking on the web. The first Core Data application I built since starting to learn how to develop applications for Mac OS X Leopard threw me for a loop because I kept getting this error message when I would try to run the application:
Core Data Error

The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store.

The problem would not happen as long as I hadn’t made any Core Data binding connections in interface builder, so I was puzzled. I looked around on the web for a while and couldn’t find any answers, so then I asked Marcus. He knew exactly what was wrong.

Apparently the Core Data applications create an XML file in which they store the data for your application in:

~/Library/Application Support/ApplicationName/ApplicationName.xml

where ~ means your home directory and ApplicationName and ApplicationName.xml are the name of your actual application.

All you have to do is delete that XML file and rebuild your app. It will then work fine as it regenerates the object model according to what is in XCode.

What happened was I had created a Core Data application and started trying to do things during which I screwed up completely and decided to scrap the whole project. I then went into the finder and deleted the application code directory completely and decided to start over. When I created the new Core Data application, I used the exact same name as the first project. So, now when I went to build the application, it went and looked in:

~/Library/Application Support/ApplicationName/ApplicationName.xml

and found the file there, so it tried to use it. Meanwhile, my data model had changed in the new project. The application didn’t know what to do with the incompatible model and then threw up its hands and gave me this error. Anyhow, it’s an interesting lesson and very helpful. Thanks to Marcus for providing the answer. Meanwhile, take a look at this code that is part of the default Core Data template:
[c]
– (NSString *)applicationSupportFolder {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
return [basePath stringByAppendingPathComponent:@”ApplicationName”];
}
[/c]
This code is returning the path to the specific Application Support directory for your application.

Hope this helps.

Getting Started with Cocoa and Objective-C

Most of my posts related to programming on this site center around programming .NET using C# on Windows. Well, it looks like that era in my programming career has come to a close. I bought a MacBook Pro back in June and have been working toward starting to learn how to develop applications for the Mac. I’ve waited until now for several reasons–one of which is that the new XCode 3.0 and Objective-C 2.0 just came out with Mac OS X Leopard. As I have time, I am going to start documenting my experiences here to help others figure things out as well. I could be wrong, but with the introduction of Mac OS X Leopard, I believe the demand for Mac developers is going to rise dramatically. Time will tell.

Anyhow, while my general programming experience is very useful to me as far as logic and flow are concerned for developing an application, the Apple way of doing things is different and takes some getting used to. From what I’ve seen so far, though, is that the Apple way is also very cool. I am fortunate to work with Marcus Zarra who is an independent software developer on the Macintosh. He is helping me close some gaps in my own knowledge and so I’m going to journal the things I’m learning here from now on.

So, lets get coding…

Digg API C# Library

I did a little search around the web for a C# library that encapsulates the Digg API and found this little gem written by Dan Atkinson. Thanks Dan. This is a nice little library. It places the data it gets back from the Digg API calls into a DataSet making it simple to use it however you want. He also wrote a helper function that allows you to get the raw XML. You can simply drop that into an XmlDocument object and use XPath queries to get the nodes you want. From the post on his blog, it sounds like Dan might be extending the library.

You can see Dan’s blog at http://www.dan-atkinson.com/blog/.

The Digg API allows you to collect all kinds of information about Digg stories, comments, users, diggs, and more. It’s quite interesting. I was thinking I might write a few apps that provide a little analysis of what kinds of comments get dugg the most in popular stoires. I’ll write the app when I get a little time. Meanwhile, thanks again, Dan, for the library.

8051 Microcontroller Programming

8-bit MCUs continue to be a popular microcontroller for embedded systems because they are fairly simple to understand and write code for. I have been pursuing embedded programming as a hobby for several years now and have found the obstacles to understanding to be pretty difficult to overcome. In the course of the past year or so, though, I haver begun taking steps to figure things out. My first step was to actually purchase a microcontroller development board along with some books. I ended up purchasing a dev board from Silicon Laboratories. The actual development kit I bought is the C8051F020DK. It provides everything you need to get started with Microcontroller development. What it doesn’t include though is a quick way for programmers to learn electronics. This continues to be my biggest challenge. Here, however, is what I’ve learned so far for those of you who are Windows programmers but would like to venture into the embedded world.

Interrupts Are Like Events
As programmers we are used to responding to events constantly. An event handler on a button or a list control that has just been changed gets triggered when a user does some function such as clicking the button or changing the selection in a list.

Things are similar in embedded programming. While embedded systems don’t have the layer upon layer of abstraction we’re used to, they do provide the tools you need to be able to do what is necessary in the embedded world. Interrupts are a special abstraction that get triggered based on a timeout or a button (a physical button on the dev board for example). Timers are a special peripheral of these chips that will count up and then trigger an interrupt when the number overflows. When that happens, we are able to figure out how much time actually elapsed and create more meaningful delays in order to achieve what we want.

While flashing an LED (Light Emitting Diode) is not terribly exciting, it is something that can be handled very easily in an embedded system using interrupts. You simply set the timer running and attach an interrupt function (similar to a callback, really) and when the timer overflows, our function gets called. It is at that point when the LED gets toggled on or off to make it flash.

Ports Turn Stuff On and Off
On the development board, I have 64 port pins that I can manage in code that turn things on and off. The C code to do so is very simple. First, we have to define the memory location of the pin we’re interested in. Bascially on my system there are 8 ports with 8 pins each. Only the lower numbered ports 0-3 are bit addressable which means that they can be turned on and off independently of the rest of the pins on the given port. Byte addressable only ports require that you maintain state and use bit shifting and/or masking to make the changes that you need.

Hooking It Up
I’ve hooked up my board to an “Electronics Learning Lab” from Radio Shack. You really don’t need to get one of those, but I had one and decided to use it. My first project was connecting it to a 7-segment display on the learning lab. For each of the LEDs in the 7 segment display, I connected a port pin from port 1. Below are the pinouts for the peripheral connections. I used Port 1, which is connected to pins C4, B4, A4, C3, B3, A3, C2, and B2. See the frame below for the complete pinout.

You can see the way the seven segment display is connected in the following illustration.

From Vast Resources to Few
Being a Windows programmer makes learning embedded systems a bit of a challenge. By specification, the 8051 only allows you a maximum of 64KB of program code. If your program is bigger than that, you are either out of luck or you have to use some embedded guru’s black magic that I am, clearly, not yet familiar with. This is a far cry from the amount of programming (and data for that matter) memory space we have at our disposal as Windows programmers.

As strange as it may seem, I really like the challenges that come with learning embedded systems. I think it makes you more likely to consider optimization in other types of programming which can be a good thing. As soon as I think of a killer project to create, I will prototype it, market it and sell the idea to highest bidder. Until then, I will just continue to fiddle around with LEDs and, well, LEDs. Does anybody have some ideas for a killer widget that is primarily LED related?

ASP .NET 2.0 TreeView Strategy

The statelessness of the web continues to challenge every web devloper no matter if you’re a newbie or a seasoned professional. Wherever I am in the development experience landscape, I am no exception.

While working with an ASP .NET 2.0 TreeView for the first time I was hopeful that there might be some built in capabilities that would make my experience similar to what you might expect in the WinForms TreeView.

When working with the TreeView in WinForms, I simply create a derived TreeNode object for every type of entity I expected to represent in the TreeView. That way, when I get a SelectedNodeChanged event, I can simply see what the node type is and respond accordingly. So I set up the event handler in my ASP .NET TreeView and went to look at the signature and realized that the only thing I was given was the sender (the TreeView itself) and a generic EventArgs object. This is different than what you get in the WinForms TreeView. It actually provides the the selected node object in the event args. Well, I reasoned, just because I’m not given the node object doesn’t mean that I can’t just grab it out of the TreeView’s SelectedNode property.

Now, keep in mind that I had created a base TreeNode type that all of my TreeNodes could inherit from, but to be safe I just typecasted to the TreeNode type when I accessed the selected node in the TreeView.
Here is what I did:
[csharp]TreeNode selectedNode = ((TreeView)sender).SelectedNode;[/csharp]
I then used the GetType() method to determine whether I was getting back the type I was expecting. And herein lies the problem. As I moused over selectedNode while debugging, the type it was showing was TreeNode. No matter which of my inherited node types I had added to the TreeView on page load, the SelectedNode parameter always returned a TreeNode type.

At this point, I realized that the statelessness of the web had gotten me again. I should have realized that this would be the case. The TreeView only knows how to make generic TreeNodes and since the TreeView has to be built on each successive trip to the server, it doesn’t know how to make my extended tree nodes.

In thinking through a bit more, and with a bit of help from some guys on the CodeProject.com message boards, I realized that the only way to do what I was wanting was to create my own derived TreeView and create a way to store the types that I needed in the ViewState. Oy! It was starting to seem that this way of handling a TreeNode getting clicked was getting much more complicated than it needed to be.

I decided to head back to the drawing board or, actually, the MSDN documentation and see what all the TreeView had to offer me. What it all boils down to are three different properties I can access. They are:

TreeNode.Text: This is the text that gets displayed for the particular node when rendered.

TreeNode.Value: This is the value of the node which has to be unique at any given hierarchy depth.

TreeNode.ValuePath: This is the full value path to the current node.

Thes properties make it pretty clear that the way to handle TreeNodes in ASP .NET 2.0 is to use the ValuePath to determine where the selected node lies in the hierarchy. The Text property is only useful in that it will display something to the user. The Value property is useful in that it will allow you to store an identifier for the current node. It’s the ValuePath property, however, that allows you to parse out everything you need to respond accordingly.

Now that I knew that I needed to determine a good way to parse the ValuePath, I stubmled upon a new problem. In the event handler for the node change, I was simply building a URL to redirect to and then calling Response.Redirect(). The problem is that if you want to simply redirect to the current page and display a View in the page (I was was using a MutiView, which is another powerful control available in ASP .NET 2.0) based on parameters you’ve passed in the URL, you’re going to have problems with the TreeView not retaining its display. The TreeView is expecting that you will simply post back to the server and it will be able to retain it’s layout in the ViewState. In my case, though, when I clicked the TreeNode, it was doing a Response.Redirect which, when the new page loads, will cause it to not be a PostBack which simply means that the TreeView will re-render in it’s default state (which is determined by the ExpandDepth property that is set in the designer or code views).

What this means is that each time I would click a node in the TreeView, when the page loaded again, my TreeView would be collapsed again to its default expansion depth which I had set to 1. So, how could I ensure that when the page loads, my TreeView is expanded properly?

I decided to take the ValuePath of the selected node and append it to the URL as a parameter as well and then use that on the other side to expand the TreeView to the same state as it was before the node was clicked. Of course, first you have to UrlEncode the ValuePath like this:

string encodedValuePath = HttpUtility.UrlEncode(valuePath);

And then you can add it to the URL you are redirecting to, but here is how I handle expanding the nodes properly when the page is loaded again:
[csharp]if (Request.Params[“ValPath”] != null)
{
// Decode the ValPath parameter
string valPath = HttpUtility.UrlDecode(Request.Params[“ValPath”]);

// Find the node we want to select according to the ValuePath
TreeNode node = tvMain.FindNode(valPath);

if (node != null)
{
// If we were able to find the node, we expand it and set its
// selected property.
node.Expand();
node.Select();

// Now we simply walk up the TreeNode hierarchy, expanding each
// parent node above us to ensure that the TreeView will display
// properly.
TreeNode tmpNode = node;
while (tmpNode.Parent != null)
{
tmpNode.Expand();
tmpNode = tmpNode.Parent;
}
}
}[/csharp]
I had orignally thought that simply selecting and expanding the selected TreeNode would be enough, but it didn’t actually work correctly until I did things this way.

You may be wondering at this point why I don’t simply use the NavigateUrl property on each node when I initially populate the TreeView. The issue is that the ValuePath property gets set at a later time and is not available when you first instantiate a TreeNode. If I tried to append the ValuePath to the NavigateUrl property at the time when the TreeNode was created, it would have thrown a null reference exception. On the other hand, when you just redirect in the node changed event handler as I was doing, you can grab the ValuePath of the currently selected node without problem.

Ok, so now that was working correctly. There was only one more thing I needed to do. I had to have a good way to parse the ValuePath property when the node changed event was fired. For this, I implemented the Strategy Pattern and created a Strategy Factory and ValuePathStrategy objects for each of the TreeNode entities. Basically, my Strategy Factory (which is a Singleton) takes the ValuePath as a parameter and determines from the first or second level in the ValuePath which type of strategy object to create and passes that object back to the caller. The caller can then simply access the Url property on the ValuePathStrategy object and redirect accordingly. Here is what the code basically looks like:
[csharp]protected void tvMain_SelectedNodeChanged(object sender, EventArgs e)
{
TreeView view = (TreeView)sender;
TreeNode node = view.SelectedNode;
if (node != null)
{
ValuePathStrategyBase vbase = ValuePathStrategyFactory.
Instance.GetStratey(node.ValuePath, view.PathSeparator);
if (vbase != null)
{
Response.Redirect( “~/Default.aspx?” + vbase.Url, true);
}
}
}[/csharp]
Notice I’ve also passed in the PathSeparator so each level of the path can be easily tokenized with the String.Split method. Each of what would have originally been extended tree nodes are now strategy objects that provide different URL parameters based on what will be needed when the page loads including the view, which specifies which view in my MultiView to display, the name of the parameter that view needs in order to load its data, and the parameter value itself which, in the case of my application, gets passed to an ObjectDataSource which has a table adapter for the corresponding data in the database.

I’m not sure that my methods for handling my TreeView are the best, however, I’m pretty happy with the outcome. I’ve been able to keep everything relatively clean code wise and the way I’ve done things seems to uphold the rules of designing an application for the web as opposed to WinForms.