You know what happens when an expensive lightweight bicycle attached firmly to the top of a two-ton vehicle moving at approximately five miles per hour hits an immovable house? Figure 1 shows the results of transforming an expensive bike into a figurative ball of aluminum foil.
It all began in a hot tub at some conference or another. The scene included four rather out-of-shape middle-aged developer/speakers, and one thirty-something-in-training-for-a-marathon interloper in far better shape than the rest of us?it wasn't a particularly pretty sight. As often seems to happen at these get-togethers among old friends, we headed down the path of, “What do you want to make sure you do before you go?” (In this case, “before you go” referred to the “final” go.) I usually keep my opinions to myself at these times, but I couldn't help but mention my goal of riding a bicycle across the continental US before the final conference session, metaphorically speaking.
The excitable young one (I'll call him “Steve Forte,” just to keep the identities anonymous), suggested that to get in training for the long ride, we should ride from Los Angeles to San Diego after Microsoft's PDC conference, in November. In a crazed moment, we all agreed. At that point, I didn't even own a bicycle.
Flash forward a month, and I've purchased a very expensive touring bike (two of them, to be exact?there are two cyclists here at home) and we've gone out for a ride around Pasadena. It's a lovely summer day, we've had a great ride, and have firmly, nay, semi-permanently attached the bicycles to the also expensive bike rack on the roof of the car in preparation for the trip home. Upon arrival at the family manse, we're talking and joking around as we do what we always do?open the garage door with the remote opener and pull into the garage. At this point, however, we are rudely surprised with a grinding, crumpling noise as we drive in. It takes a second for the horrible truth to sink in, and as we climb out of the car, I was living in some fantasy that we'd find the bikes intact somehow. I'm not sure how this could be physically possible, but that's what I dreamed. Figure 1, as you've seen, shows the wreckage that we found, spread out for your spectation. (No, I didn't make up that word. I heard it one day as the radio traffic announcer described all the folks slowing down to watch the scene of a wreck, as in, “The freeway traffic has slowed to a crawl due to the large amount of spectation.” No kidding. But back to the story…)
As we discovered back at our bike dealer, this happens often. They estimated that they see crumpled bikes like this, lost for the very same reason, at least once per week. Luckily, because the frames are so light and so, well, “crumpleable,” none of the other components were damaged, and we just replaced the frames and re-used all the old parts. Of course, the frames constitute over three-quarters of the cost of the bikes. You can cry about such things, or you can laugh. Since no one was hurt, and the house and car suffered no damage, we voted for the latter. The bikes are back in use, no worse for the wear. We didn't, however, replace the ruined roof rack. Those bikes now go into the back of the station wagon. The moral of this story: You need to be aware of where you are, all the time.
Speaking of knowing where you are, I recently embarked on a project to create a component that you can drop onto a Windows Form in Visual Studio .NET. The component needed to be able to communicate with the host form at design time, and I spent hour after hour looking for some property of the component, or its container, that would hand me a reference to the form itself. This seemed like an obvious need, and something that should be easy to retrieve?the component is sitting right there on the form, how hard could it be to obtain a reference to that form?
Apparently, retrieving that reference to the parent form is more difficult than you might at first imagine. On the other hand, creating a drop-in component is simple, and you can create one yourself. As a demonstration, here's the simplest example I could come up with: Say you want a component that you could drop on a form, so that at runtime, you could call the SetColor method of the component passing in a color and magically set the BackColor property of all the controls on the form to be the color you selected. Yes, it's somewhat contrived, but it proves how easy it is to create a component.
To get started, create a new Windows Forms project using Visual Basic .NET. (Obviously, this could work just as well using C#, but I'm going to use VB .NET to describe this process.) Name the project TestComponent. Modify the project properties, setting the Output Type option to be Class Library.
Now delete Form1.vb. (The reason you created a Windows Forms project, as opposed to a class library, is to avoid having to set references to all the assemblies you'll need in order to work with forms and controls.) Add a new class to the project named SetBackColor, and modify the code so that it looks like this:
Imports System.ComponentModel
Imports System.ComponentModel.Design
Public Class SetBackColor
Inherits Component
Private frm As Form
Public Sub SetColor(ByVal clr As Color)
If Not frm Is Nothing Then
For Each ctl As Control In frm.Controls
Try
ctl.BackColor = clr
Catch
' If the property setting fails,
' just keep going.
End Try
Next
End If
End Sub
End Class
That seems simple enough, but you still need to set the value of the variable frm, which maintains a reference to the host form, and there lies the difficulty.
You need some way to get a reference to the host form, but you only really want to do the work at design time. That is, your goal is to end up with a line of code like this, within the form's serialized stream, assuming that the instance of your component on the form is named SetBackColor1:
Me.SetBackColor1.Form = Me
When you plop the component onto the form, you need it to figure out the reference to the parent form, and save that value with the form. That way, at runtime, the code shown here runs, and simply assigns the reference to the form into the Form property of the component instance.
I found nothing in the Help files on this topic. I posted questions on several public newsgroups. I asked people that had written their own components. No one had an answer for what seemed, to me, to be an obvious question. Finally, my business partner, Andy Baron, suggested one way to find the answer. Clearly, he thought, other components needed this same information. The Timer component, for example, knows what form it's hosted on. Andy suggested that I use Anakrino to decompile the appropriate assembly to see how the Timer component does its work. (Anakrino is an indispensable tool for deciphering what's going on inside .NET code. It decompiles .NET IL back into C#. Go to http://www.saurik.com/net/exemplar for more information.)
Once I looked into how the Timer component finds its reference to the host form, I had my component working within minutes. To finish your simple component, add this code to your class:
<Browsable(False)> _
Public Property Form() As Form
Get
If Me.Site.DesignMode Then
Dim dh As IDesignerHost = _
DirectCast(Me.GetService( _
GetType(IDesignerHost)), IDesignerHost)
If Not dh Is Nothing Then
Dim obj As Object = dh.RootComponent
If Not obj Is Nothing Then
Me.Form = DirectCast(obj, Form)
End If
End If
End If
Return frm
End Get
Set(ByVal Value As Form)
If Not Value Is Nothing Then
frm = Value
End If
End Set
End Property
The code counts on determining if it's running in design mode, and if so, retrieves the IDesignerHost object returned by calling the GetService method of the component. For more details, see the online help. By putting this code in the Form property “getter,” the host form will call this code when you first place the component on the form in design view. Because the property has been set so that it won't appear in the Properties window (by setting the Browsable attribute value to False) it doesn't show up in the Properties window, but its value still gets serialized with the form.
To try out your component, compile the project. Either add a new Windows Form project to the solution or open another instance of Visual Studio .NET. Modify the Toolbox window, adding your new component to the Components tab in the toolbox. Drag the SetBackColor component onto your form. Like any other component, it should appear in the tray area of the form. Add a few controls to the form including a button, and modify the button's Click event handler to run code like this:
SetBackColor1.SetColor(Color.Blue)
Run the project and click the button. If you've followed the steps carefully, your component should be able to communicate with its parent form and set the BackColor property of each of the controls on the form.
As you've seen here, it's important to be able to determine where a component is hosted, but it may not be easy to find that information. With two expensive bikes on the top of your car, it's vital that you keep track of where you are, as well. By the time you read this, I'll either have made the trip from Los Angeles to San Diego, or suffered greatly trying. I'll let you know how it went.