Debugging Design-Time Controls

 

Steve Lasker
Immedient

November 2002

Applies to:
   Microsoft® Visual Studio® .NET
   Microsoft Windows® Forms
   Microsoft® ASP.NET
   Microsoft® Mobile Internet Toolkit
   Microsoft® .NET Compact Framework

Summary: Walks through the process of debugging any custom design-time control when it is being used with the Microsoft Visual Studio .NET designer, and describes the chain of events that take place when you work with controls in the design surface. (13 printed pages)

Download Designtimedebugging.exe.

Contents

Introduction
Setting Up the Environment
Creating the Blank Solution
Creating a Control Library
Creating the Windows Forms Host Project
Starting Our Debugging Session
Chain of Events
Summary

Introduction

An important part of developing any design-time control is the ability to troubleshoot and debug your code. When working with a control or Microsoft® Windows® Form in the design-time environment of Microsoft Visual Studio® .NET, your code is being executed even though the project does not appear to be running. This can pose a challenge as far as debugging goes. If you are creating any type of object that will have a design-time view, this article shows you how you can catch errors and debug when your object is being used within the Visual Studio designer. I will also describe some of the chain of events that happen when you work with controls in the design surface. The best part is that you don't have to create any wrapper objects. The Visual Studio .NET team baked the debugging environment right in.

To show the steps of troubleshooting and debugging your code as you change values or drop controls onto the design surface, we'll create an example base Windows Form that has a string property. When we change the value of the property, we'll step into the debugger.

In order to minimize the potential for errors due to lack of specifics, all files and directories have specific names. You're more then welcome to change these as you'd like. However, for clarity and ease of demonstration, I'll specify everything in detail.

Setting Up the Environment

The first step is to create an environment to host our code. We'll create:

  • A blank solution to host our projects.
  • A control library to host our compiled control.
  • A Windows Forms project to host our custom control.

Creating the Blank Solution

  1. On the File menu, click New, and then click Blank Solution.

  2. Name the solution DesignTimeDebugging.

  3. Place the solution in c:\ DesignTimeDebugging.

    Figure 1. Creating a blank solution to contain several projects

Creating a Control Library

Since our purpose is to demonstrate debugging, not to create custom controls, we won't worry about creating an elaborate control. We'll just create a simple Form with a bogus property.

Create the Control Project

  1. On the File menu, click New, and then click Project.

  2. In the left window, click either Visual Basic Projects or Visual C# Projects.

  3. In the right window, click Windows Control Library.

  4. Name the control library Immedient.Windows.Forms. Notice how we follow the same convention as Microsoft. The only difference is that instead of "System" we're using "Immedient." This identifies a unique namespace, yet with consistency.

  5. Delete UserControl1.

    Figure 2. Adding a control library for your design-time control

Add a new Form

  1. Right-click the Control Library project. Click Add, and then click Add Windows Form.

  2. Name the Form "Form."

    Note We're purposely naming the Form with a collision to demonstrate namespaces.

  3. Add the following code to the body of the class definition:

    Visual Basic .NET
    Private _myText As String
    ''' <summary>
    ''' A Custom property used for this silly sample
    ''' </summary>
    ''' <value>A value of no meaning</value>
    <Description("A Custom property used for this silly sample"), _
    DefaultValue("Hello"), _
    Category("Appearance")> _
    Public Property MyText() As String
        Get
            Return _myText
        End Get
        Set(ByVal Value As String)
            If (_myText <> Value) Then
                _myText = Value
            End If
        End Set
    End Property
    
    C#
    private string _myText = "Hello";
    /// <summary>
    /// A Custom property used for this silly sample
    /// </summary>
    /// <value>A value of no meaning</value>
    [
       Description("A Custom property used for this silly sample"),
       DefaultValue("Hello"),
       Category("Appearance")
    ]
    public string MyText
    {
       get{return _myText;}
       set
       {
          if(_myText != value)
          {
             _myText = value;
          }
       }
    }
    

Code breakdown

Private _myText As String = "Hello"; This is a private variable to hold the contents of our public property.

Property attributes

[ ] in C# or <> in Visual Basic .NET In C#, all code between the square brackets (<> in Microsoft® Visual Basic® .NET) are attributes. These inform the common language runtime (CLR) to behave in a given way once constructed. Attributes are sort of a cross between a constructor and pre-processor directive. They're set at coding time, and can't be changed at run time.

Description("A Custom property used for this silly sample") This adds a description to the bottom of the property sheet for your property. We usually duplicate the same text from our XML summary documentation. (Hopefully the product team will merge these two declarations in the future.)

Figure 3. Your custom property, with its description, displayed on the property sheet

DefaultValue("Hello")This is used in combination with the default value of our private variable. If the value of the property doesn't match the value in the DefaultValue attribute, the property sheet will show the value in bold. Visual Studio .NET will also generate code in the InitializeComponent() method to set the value. If the property sheet value matches the value in the DefaultValue attribute, the property sheet value will not be displayed in bold, and the InitializeComponent() will not have any reference to the property. This is great for inheritance. If none of the derived objects changed this property, they will inherit any changes you make to the base class. This can be a very powerful feature when used properly.

Category("Appearance")This value is used to sort the properties in the property sheet.

Creating the Windows Forms Host Project

  1. On the File menu, click New, and then click Project.
  2. In the left window, click either Visual Basic Projects or Visual C# Projects.
  3. In the right window, click Windows Application. (This will host our sample.)
  4. Name the project DesignTimeDebugging.
  5. Be sure to verify that the radio button is set to Add to Solution

Add a Project Reference to Our Control Library

  1. Within our Host App Project, right-click References, and click Add Reference.

  2. On the Projects tab, select our Immedient.Windows.Forms project.

    Figure 4. Adding a project reference to your design-time control

Using Our Custom Form

  1. Recompile the project to provide Microsoft IntelliSense®.

  2. Change the inheritance of Form1 by changing the following code, in bold, to inherit from the Form we just created.

    Visual Basic .NET
    Public Class Form1
        Inherits Immedient.Windows.Forms.FormC#
    namespace Immedient.Samples.HostApp.Windows
    {
       public class Form1 : Immedient.Windows.Forms.Form
       {
    
  3. Open the Form1, and notice the custom property on the Property Sheet. If you receive errors, close all open files, click Rebuild Solution, and then open the Form again in design mode.

Setting Control Library Project Properties

There are a couple of properties you'll want to set, and one to verify on our control library project.

You'll want to confirm that you're actually generating the required debugging information for Visual Studio .NET to step into your code. If you get the "?" symbol on your breakpoints when you start the debugging session, you're not generating the appropriate symbol files. This may be because you're in release mode, or because the setting was changed accidentally. Be sure the Generate Debugging Information property is set to true.

XML documentation (C# only)

The following settings will only apply to C# projects. In Visual Studio .NET 2002, Visual Basic .NET does not have a means to generate XML documentation.

In order to benefit from the C# XML documentation we created, you need to tell Visual Studio .NET to generate an XML file. In the XML Documentation File property, set the value to the same name as your assembly with the xml extension. (It would be nice if they just had a checkbox for this, but oh well...)

Finally, there's one more trick about XML documentation. Once you set the value for XML Documentation File, Visual Studio .NET is very helpful in warning you of every public interface that doesn't have documentation assigned. This gets real annoying, real fast. So, if you're not ready to deal with all the public interface documentation, you can set the Warning Level to 2. The task list won't be cluttered with warnings until you're ready to address them by filling in the documentation.

Click here for larger image.

Figure 5. Setting properties related to XML documentation in a C# project

Starting Our Debugging Session

Now that we have some code to debug, let's start the session. In order to debug our code, we need to step into the application that is hosting our code. In this case it's Visual Studio .NET.

Note Visual Studio .NET was designed, just like the rest of the .NET Framework, from the ground up to host the design environment. Remember, the product team used the same set of tools we use. All we need to do is to start our class library within Visual Studio .NET. We do this with some properties on the Project.

Setting the Debugging Properties

Within the Debugging section, there are a couple of properties we need to change. They are slightly different in Visual Basic .NET and C#:

Visual Basic .NET Project Property Sheet

  1. Right-click on the Control Library project and click Properties.

  2. Click Configuration Properties and select the Debugging sub option.

  3. Change the Start Action to Start External Program:, and enter the path of the Visual Studio .NET IDE: C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe.

    Click here for larger image.

    Figure 6. Setting the startup program to host your debugging session (Visual Basic .NET)

C# Project Property Sheet

  1. Right-click on the Control Library project and click Properties.

  2. Click Configuration Properties.

  3. Change the Debug Mode to Program.

  4. Change the Start Application to Visual Studio .NET. The default location is: C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe.

    Click here for larger image.

    Figure 7. Setting the startup program to host your debugging session (C#)

Setting the Breakpoint

Set a breakpoint on theif statementwithin the set of the property.

Note You won't want to set a breakpoint on the get without setting an assert, as the design-time environment will "get" the value many more times than you want to hit F5.

Figure 8. Setting the breakpoint in your custom property

One Last Thing

Be sure the Control Library project is the Startup Project. Set this by right-clicking the Control Library project and then click Set as Startup Project.

Fire Away

  1. Start the debugging session by hitting F5. Notice how a new instance of Visual Studio .NET has launched. Your development environment is hosting Visual Studio .NET within it—pretty cool.
  2. An easy way to tell which instance of Visual Studio .NET is your hosting debug environment is to notice the debugging buttons; it's the one with the start button grayed out.
  3. Once Visual Studio .NET starts up again, open the same solution: C:\DesignTimeDebugging\DesignTimeDebugging.sln.
  4. Within the HostApp Windows Forms project, open Form1 by double-clicking Form1 in the Solution Explorer.
  5. Within the property sheet, change the value of the MyText property to Good Bye.
  6. You should step into your code.
  7. You have just debugged your first .NET design-time control. Let's take a look at the chain of events that happen in the background.

Chain of Events

When using design-time controls, it's important to know what happens behind the scenes when you drop a control on your design surface—or for controls such as a Form, what happens when you create a Form that is inherited from another Form you created.

When an object is opened in the design environment, the class that the object is inherited from (not the newly created class) is constructed by Visual Studio .NET. Remember, a control that is created fires its constructor, which also fires the InitializeComponent() method within the base class's constructor. Once the derived class is constructed, that magically named method within your newly created class, InitializeComponent(), is parsed line-by-line by Visual Studio .NET. The only thing magic about this method is its name. Visual Studio .NET just knows to look for this method.

In Visual Basic .NET, if Visual Studio .NET can't understand the line of code, the line is removed (a nice way of saying eaten). The Visual Basic team felt it was more important to maintain the design-time environment. The C# team felt it was more important to maintain the code, so if Visual Studio .NET can't parse InitializeComponent(), you'll get the text of the exception presented to you. This includes inherited Forms, or controls placed on the design surface of another component class control.

Any property that is part of your base object is set by the base object and its constructor. When the InitializeComponent() method runs, the values are changed to the values you have set on the property sheet. In Visual Studio .NET, the property sheet is just a graphical representation of the InitializeComponent() method. If your base class performs some sort of functionality in the constructor, be careful; it will be executed in the design environment as well.

You do have some control over this, however. Any class that derives from the Component class has a property called DesignMode that is set to true when the code is executing within the constructs of the Visual Studio .NET designer. So you have the option to wrap code within an if statement. There's one more trick, however. The DeisgnMode property isn't set to true in the constructor. Remember, there's no real magic here. Visual Studio .NET creates your object as it parses the InitializeComponent() method. Once the object is constructed, Visual Studio .NET keeps track of the objects it creates, and simply says:

newlyCreatedObject.DesignMode = true

To have some fun and see what events fire, and when the DesignMode property is set, drop the following code in your Immedient.Windows.Forms.Form. In order to get the events to fire in Visual Studio .NET, you'll need to close all the forms, and then recompile your solution.

Visual Basic .NET
Private Sub Form_Layout(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.LayoutEventArgs) _
    Handles MyBase.Layout
    MessageBox.Show("layout: DesignMode=" + _
        Me.DesignMode.ToString())
End Sub

Private Sub Form_Load(ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles MyBase.Load
    If Me.DesignMode Then
        ' Don't connect to the database at design time
        MessageBox.Show("Form Load: DesignMode=" + _
            Me.DesignMode.ToString())
    Else
        ' Make a connection to the database, and do something
        MessageBox.Show("Form Load: DesignMode=" + _
            Me.DesignMode.ToString())
    End If
End Sub

Private Sub Form_VisibleChanged(ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles MyBase.VisibleChanged
    MessageBox.Show("VisibleChanged: DesignMode=" + _
        Me.DesignMode.ToString())
End Sub

Private Sub Form_Paint(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles MyBase.Paint
    MessageBox.Show("Paint: DesignMode=" + _
        Me.DesignMode.ToString())
End Sub

Summary

In this article, we used a simple property on a Windows Form. However, anything that changes in the design-time environment can be debugged. I originally found this technique when developing a Microsoft® ASP.NET Image HREF design-time control. This same technique applies to component controls, like the EventLog and Data controls that display in the system tray as well. Visual Studio .NET provides a rich, productive development environment for developing controls, in which it's just as easy to create and debug controls as it is to consume them.

About the Author

Steve Lasker is the National Director of Research & Development at Immedient. Steve has been developing .NET controls and frameworks for Windows Forms, ASP.NET, and the .NET Compact Framework. Steve can be reached at Steve.Lasker@Immedient.com.