Walkthrough: Debugging WPF Custom Controls at Design Time

This walkthrough shows how to debug a design-time adorner for a Windows Presentation Foundation (WPF) custom control. The adorner is a check box, which provides a simple auto-size feature.

In this walkthrough, you perform the following tasks:

  • Create a WPF custom control library project.

  • Create a separate assembly for design-time metadata.

  • Implement the adorner provider.

  • Test the control at design time.

  • Set up the project for design-time debugging.

  • Debug the control at design time.

When you are finished, you will know how to debug an adorner for a custom control.

Note

The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Working with Settings.

Prerequisites

You need the following components to complete this walkthrough:

  • Visual Studio 2010.

Creating the Custom Control

The first step is to create the project for the custom control. The control is a button with small amount of design-time code, which uses the GetIsInDesignMode method to implement a design-time behavior.

To create the custom control

  1. Create a new WPF Custom Control Library project in Visual Basic or Visual C# named AutoSizeButtonLibrary.

    The code for CustomControl1 opens in the Code Editor.

  2. In Solution Explorer, change the name of the code file to AutoSizeButton.cs or AutoSizeButton.vb. If a message box appears that asks if you want to perform a rename for all references in this project, click Yes.

  3. Open AutoSizeButton.cs or AutoSizeButton.vb in the Code Editor.

  4. Replace the automatically generated code with the following code. This code inherits from Button and displays the text "Design mode active" when the button appears in the designer.

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.Windows.Controls
    Imports System.Windows.Media
    Imports System.ComponentModel
    
    ' The AutoSizeButton control implements a button
    ' with a custom design-time experience. 
    Public Class AutoSizeButton
        Inherits Button
    
        Public Sub New()
            ' The following code enables custom design-mode logic.
            ' The GetIsInDesignMode check and the following design-time 
            ' code are optional and shown only for demonstration.
            If DesignerProperties.GetIsInDesignMode(Me) Then
                Content = "Design mode active"
            End If
    
        End Sub
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.ComponentModel;
    
    namespace AutoSizeButtonLibrary
    {
        // The AutoSizeButton control implements a button
        // with a custom design-time experience. 
        public class AutoSizeButton : Button
        {
            public AutoSizeButton()
            {
                // The following code enables custom design-mode logic.
                // The GetIsInDesignMode check and the following design-time 
                // code are optional and shown only for demonstration.
                if (DesignerProperties.GetIsInDesignMode(this))
                {
                    Content = "Design mode active";
                }
            }
        }
    }
    
  5. Set the project's output path to "bin\".

  6. Build the solution.

Creating the Design-time Metadata Assembly

Design-time code is deployed in special metadata assemblies. For this walkthrough, the custom adorner is deployed in an assembly named AutoSizeButtonLibrary.VisualStudio.Design. For more information, see Providing Design-time Metadata.

To create the design-time metadata assembly

  1. Add a new Class Library project in Visual Basic or Visual C# named AutoSizeButtonLibrary.VisualStudio.Design to the solution.

  2. Set the project's output path to "..\AutoSizeButtonLibrary\bin\". This keeps the control's assembly and the metadata assembly in the same folder, which enables metadata discovery for designers.

  3. Add references to the following WPF assemblies.

    • PresentationCore

    • PresentationFramework

    • System.Xaml

    • WindowsBase

  4. Add references to the following WPF Designer assemblies.

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Add a reference to the AutoSizeButtonLibrary project.

  6. In Solution Explorer, change the name of the Class1 code file to Metadata.cs or Metadata.vb. If a message box appears that asks if you want to perform a rename for all references in this project, click Yes.

  7. Replace the automatically generated code with the following code. This code creates an AttributeTable which attaches the custom design-time implementation to the AutoSizeButton class.

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.ComponentModel
    Imports System.Windows.Media
    Imports System.Windows.Controls
    Imports System.Windows
    
    Imports AutoSizeButtonLibrary
    Imports Microsoft.Windows.Design.Features
    Imports Microsoft.Windows.Design.Metadata
    Imports AutoSizeButtonLibrary.VisualStudio.Design
    
    ' The ProvideMetadata assembly-level attribute indicates to designers
    ' that this assembly contains a class that provides an attribute table. 
    <Assembly: ProvideMetadata(GetType(AutoSizeButtonLibrary.VisualStudio.Design.Metadata))> 
    
    ' Container for any general design-time metadata to initialize.
    ' Designers look for a type in the design-time assembly that 
    ' implements IProvideAttributeTable. If found, designers instantiate
    ' this class and access its AttributeTable property automatically.
    Friend Class Metadata
        Implements IProvideAttributeTable
    
        ' Accessed by the designer to register any design-time metadata.
        Public ReadOnly Property AttributeTable() As AttributeTable _
            Implements IProvideAttributeTable.AttributeTable
            Get
                Dim builder As New AttributeTableBuilder()
    
                builder.AddCustomAttributes( _
                    GetType(AutoSizeButton), _
                    New FeatureAttribute(GetType(AutoSizeAdornerProvider)))
    
                Return builder.CreateTable()
            End Get
        End Property
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows;
    
    using AutoSizeButtonLibrary;
    using Microsoft.Windows.Design.Features;
    using Microsoft.Windows.Design.Metadata;
    using AutoSizeButtonLibrary.VisualStudio.Design;
    
    // The ProvideMetadata assembly-level attribute indicates to designers
    // that this assembly contains a class that provides an attribute table. 
    [assembly: ProvideMetadata(typeof(AutoSizeButtonLibrary.VisualStudio.Design.Metadata))]
    namespace AutoSizeButtonLibrary.VisualStudio.Design
    {
        // Container for any general design-time metadata to initialize.
        // Designers look for a type in the design-time assembly that 
        // implements IProvideAttributeTable. If found, designers instantiate 
        // this class and access its AttributeTable property automatically.
        internal class Metadata : IProvideAttributeTable
        {
            // Accessed by the designer to register any design-time metadata.
            public AttributeTable AttributeTable
            {
                get 
                {
                    AttributeTableBuilder builder = new AttributeTableBuilder();
    
                    builder.AddCustomAttributes(
                        typeof(AutoSizeButton),
                        new FeatureAttribute(typeof(AutoSizeAdornerProvider)));
    
                    return builder.CreateTable();
                }
            }
        }
    }
    
  8. Save the solution.

Implementing the Adorner Provider

The adorner provider is implemented in a type named AutoSizeAdornerProvider. This adorner FeatureProvider enables setting the control's Height and Width properties at design time.

To implement the adorner provider

  1. Add a new class named AutoSizeAdornerProvider to the AutoSizeButtonLibrary.VisualStudio.Design project.

  2. In the Code Editor for AutoSizeAdornerProvider, replace the automatically generated code with the following code. This code implements a PrimarySelectionAdornerProvider which provides an adorner based on a CheckBox control.

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.Windows.Input
    Imports System.Windows
    Imports System.Windows.Automation
    Imports System.Windows.Controls
    Imports System.Windows.Media
    Imports System.Windows.Shapes
    Imports Microsoft.Windows.Design.Interaction
    Imports Microsoft.Windows.Design.Model
    
    ' The following class implements an adorner provider for the 
    ' AutoSizeButton control. The adorner is a CheckBox control, which 
    ' changes the Height and Width of the AutoSizeButton to "Auto",
    ' which is represented by Double.NaN.
    Public Class AutoSizeAdornerProvider
        Inherits PrimarySelectionAdornerProvider
    
        Private settingProperties As Boolean
        Private adornedControlModel As ModelItem
        Private autoSizeCheckBox As CheckBox
        Private autoSizeAdornerPanel As AdornerPanel
    
        ' The constructor sets up the adorner control. 
        Public Sub New()
            autoSizeCheckBox = New CheckBox()
            autoSizeCheckBox.Content = "AutoSize"
            autoSizeCheckBox.IsChecked = True
            autoSizeCheckBox.FontFamily = AdornerFonts.FontFamily
            autoSizeCheckBox.FontSize = AdornerFonts.FontSize
            autoSizeCheckBox.Background = CType( _
                AdornerResources.FindResource(AdornerColors.RailFillBrushKey),  _
                Brush)
        End Sub
    
        ' The following method is called when the adorner is activated.
        ' It creates the adorner control, sets up the adorner panel,
        ' and attaches a ModelItem to the AutoSizeButton.
        Protected Overrides Sub Activate(ByVal item As ModelItem)
    
            ' Save the ModelItem and hook into when it changes.
            ' This enables updating the slider position when 
            ' a new background value is set.
            adornedControlModel = item
            AddHandler adornedControlModel.PropertyChanged, AddressOf AdornedControlModel_PropertyChanged
    
            ' All adorners are placed in an AdornerPanel
            ' for sizing and layout support.
            Dim panel As AdornerPanel = Me.Panel
    
            ' Set up the adorner's placement.
            AdornerPanel.SetAdornerHorizontalAlignment(autoSizeCheckBox, AdornerHorizontalAlignment.OutsideLeft)
            AdornerPanel.SetAdornerVerticalAlignment(autoSizeCheckBox, AdornerVerticalAlignment.OutsideTop)
    
            ' Listen for changes to the checked state.
            AddHandler autoSizeCheckBox.Checked, AddressOf autoSizeCheckBox_Checked
            AddHandler autoSizeCheckBox.Unchecked, AddressOf autoSizeCheckBox_Unchecked
    
            ' Run the base implementation.
            MyBase.Activate(item)
    
        End Sub
    
        ' The Panel utility property demand-creates the 
        ' adorner panel and adds it to the provider's 
        ' Adorners collection.
        Public ReadOnly Property Panel() As AdornerPanel
            Get
                If Me.autoSizeAdornerPanel Is Nothing Then
                    Me.autoSizeAdornerPanel = New AdornerPanel()
    
                    ' Add the adorner to the adorner panel.
                    Me.autoSizeAdornerPanel.Children.Add(autoSizeCheckBox)
    
                    ' Add the panel to the Adorners collection.
                    Adorners.Add(autoSizeAdornerPanel)
    
                End If
    
                Return Me.autoSizeAdornerPanel
            End Get
        End Property
    
        ' The following code handles the Checked event.
        ' It autosizes the adorned control's Height and Width.
        Sub autoSizeCheckBox_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs) 
            Me.SetHeightAndWidth(True)
        End Sub
    
    
        ' The following code handles the Unchecked event.
        ' It sets the adorned control's Height and Width to a hard-coded value.
        Sub autoSizeCheckBox_Unchecked(ByVal sender As Object, ByVal e As RoutedEventArgs) 
            Me.SetHeightAndWidth(False)
        End Sub
    
        ' The SetHeightAndWidth utility method sets the Height and Width
        ' properties through the model and commits the change.
        Private Sub SetHeightAndWidth(ByVal [auto] As Boolean) 
    
            settingProperties = True
    
            Dim batchedChange As ModelEditingScope = adornedControlModel.BeginEdit()
            Try
                Dim widthProperty As ModelProperty = adornedControlModel.Properties("Width")
    
                Dim heightProperty As ModelProperty = adornedControlModel.Properties("Height")
    
                If [auto] Then
                    widthProperty.ClearValue()
                    heightProperty.ClearValue()
                Else
                    widthProperty.SetValue(20.0)
                    heightProperty.SetValue(20.0)
                End If
    
                batchedChange.Complete()
            Finally
                batchedChange.Dispose()
                settingProperties = False
            End Try
    
        End Sub
    
        ' The following method deactivates the adorner.
        Protected Overrides Sub Deactivate()
    
            RemoveHandler adornedControlModel.PropertyChanged, _
                AddressOf AdornedControlModel_PropertyChanged
    
            MyBase.Deactivate()
    
        End Sub
    
    
        ' The following method handles the PropertyChanged event.
        Sub AdornedControlModel_PropertyChanged( _
            ByVal sender As Object, _
            ByVal e As System.ComponentModel.PropertyChangedEventArgs)
    
            If settingProperties Then Return
    
            If e.PropertyName = "Height" Or e.PropertyName = "Width" Then
                Dim h As Double = CType(adornedControlModel.Properties("Height").ComputedValue, Double)
                Dim w As Double = CType(adornedControlModel.Properties("Width").ComputedValue, Double)
    
                If Double.IsNaN(h) And Double.IsNaN(w) Then
                    autoSizeCheckBox.IsChecked = True
                Else
                    autoSizeCheckBox.IsChecked = False
                End If
            End If
    
        End Sub
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Input;
    using System.Windows;
    using System.Windows.Automation;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using Microsoft.Windows.Design.Interaction;
    using Microsoft.Windows.Design.Model;
    
    namespace AutoSizeButtonLibrary.VisualStudio.Design
    {
        // The following class implements an adorner provider for the 
        // AutoSizeButton control. The adorner is a CheckBox control, which 
        // changes the Height and Width of the AutoSizeButton to "Auto",
        // which is represented by double.NaN.
        public class AutoSizeAdornerProvider : PrimarySelectionAdornerProvider
        {
            bool settingProperties;
            private ModelItem adornedControlModel;
            CheckBox autoSizeCheckBox;
            AdornerPanel autoSizeAdornerPanel;
    
            // The constructor sets up the adorner control. 
            public AutoSizeAdornerProvider()
            {
                autoSizeCheckBox = new CheckBox();
                autoSizeCheckBox.Content = "AutoSize";
                autoSizeCheckBox.IsChecked = true;
                autoSizeCheckBox.FontFamily = AdornerFonts.FontFamily;
                autoSizeCheckBox.FontSize = AdornerFonts.FontSize;
                autoSizeCheckBox.Background = AdornerResources.FindResource(
                    AdornerColors.RailFillBrushKey) as Brush;
            }
    
            // The following method is called when the adorner is activated.
            // It creates the adorner control, sets up the adorner panel,
            // and attaches a ModelItem to the AutoSizeButton.
            protected override void Activate(ModelItem item)
            {
                // Save the ModelItem and hook into when it changes.
                // This enables updating the slider position when 
                // a new background value is set.
                adornedControlModel = item;
                adornedControlModel.PropertyChanged += 
                    new System.ComponentModel.PropertyChangedEventHandler(
                        AdornedControlModel_PropertyChanged);
    
                // All adorners are placed in an AdornerPanel
                // for sizing and layout support.
                AdornerPanel panel = this.Panel;
    
                // Set up the adorner's placement.
                AdornerPanel.SetAdornerHorizontalAlignment(autoSizeCheckBox, AdornerHorizontalAlignment.OutsideLeft);
                AdornerPanel.SetAdornerVerticalAlignment(autoSizeCheckBox, AdornerVerticalAlignment.OutsideTop);
    
                // Listen for changes to the checked state.
                autoSizeCheckBox.Checked += new RoutedEventHandler(autoSizeCheckBox_Checked);
                autoSizeCheckBox.Unchecked += new RoutedEventHandler(autoSizeCheckBox_Unchecked);
    
                // Run the base implementation.
                base.Activate(item);
            }
    
            // The Panel utility property demand-creates the 
            // adorner panel and adds it to the provider's 
            // Adorners collection.
            private AdornerPanel Panel
            {
                get
                {
                    if (this.autoSizeAdornerPanel == null)
                    {
                        autoSizeAdornerPanel = new AdornerPanel();
    
                        // Add the adorner to the adorner panel.
                        autoSizeAdornerPanel.Children.Add(autoSizeCheckBox);
    
                        // Add the panel to the Adorners collection.
                        Adorners.Add(autoSizeAdornerPanel);
                    }
    
                    return this.autoSizeAdornerPanel;
                }
            }
    
            // The following code handles the Checked event.
            // It autosizes the adorned control's Height and Width.
            void autoSizeCheckBox_Checked(object sender, RoutedEventArgs e)
            {
                this.SetHeightAndWidth(true);
            }
    
            // The following code handles the Unchecked event.
            // It sets the adorned control's Height and Width to a hard-coded value.
            void autoSizeCheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                this.SetHeightAndWidth(false);
            }
    
            // The SetHeightAndWidth utility method sets the Height and Width
            // properties through the model and commits the change.
            private void SetHeightAndWidth(bool autoSize)
            {
                settingProperties = true;
    
                try
                {
                using (ModelEditingScope batchedChange = adornedControlModel.BeginEdit())
                {
                    ModelProperty widthProperty =
                        adornedControlModel.Properties["Width"];
    
                    ModelProperty heightProperty =
                        adornedControlModel.Properties["Height"];
    
                    if (autoSize)
                    {
                        widthProperty.ClearValue();
                        heightProperty.ClearValue();
                    }
                    else
                    {
                        widthProperty.SetValue(20d);
                        heightProperty.SetValue(20d);
                    }
    
                    batchedChange.Complete();
                }
                }
                finally { settingProperties = false; }
            }
    
            // The following method deactivates the adorner.
            protected override void Deactivate()
            {
                adornedControlModel.PropertyChanged -= 
                    new System.ComponentModel.PropertyChangedEventHandler(
                        AdornedControlModel_PropertyChanged);
    
                base.Deactivate();
            }
    
            // The following method handles the PropertyChanged event.
            void AdornedControlModel_PropertyChanged(
                object sender, 
                System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (settingProperties)
                {
                    return;
                }
    
                if (e.PropertyName == "Height" || e.PropertyName == "Width")
                {
                    double h = (double)adornedControlModel.Properties["Height"].ComputedValue;
                    double w = (double)adornedControlModel.Properties["Width"].ComputedValue;
    
                    autoSizeCheckBox.IsChecked = (h == double.NaN && w == double.NaN) ? true : false;
                }
            }
        }
    }
    
  3. Build the solution.

Testing the Design-time Implementation

You can use the AutoSizeButton control as you would use any other WPF control. The WPF Designer handles the creation of all design-time objects.

To test the design-time implementation

  1. Add a new WPF Application project named DemoApplication to the solution.

    MainWindow.xaml opens in the WPF Designer.

  2. Add a reference to the AutoSizeButtonLibrary project.

  3. In XAML view, replace the automatically generated code with the following code. This XAML adds a reference to the AutoSizeButtonLibrary namespace and adds the AutoSizeButton custom control. The button appears in Design view with the text "Design mode active", indicating that it is in design mode. If the button does not appear, you might have to click the Information bar at the top of the designer to reload the view.

    <Window x:Class="DemoApplication.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ab="clr-namespace:AutoSizeButtonLibrary;assembly=AutoSizeButtonLibrary"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <ab:AutoSizeButton Height="Auto" Width="Auto" />
        </Grid>
    </Window>
    
  4. In Design view, click the AutoSizeButton control to select it.

    A CheckBox control appears above the AutoSizeButton control.

  5. Uncheck the check box adorner.

    The control reduces in size. The check box adorner moves to maintain its position relative to the adorned control.

Setting Up the Project for Design-Time Debugging

At this point, you have completed the design-time implementation. Now you can use Visual Studio to set breakpoints and step into your design-time code. To debug your design-time implementation, you attach another instance of Visual Studio to your current Visual Studio session.

To set up project for design-time debugging

  1. In Solution Explorer, right-click the DemoApplication project and select Set as StartUp Project.

  2. In Solution Explorer, right-click the DemoApplication project and select Properties.

  3. In the DemoApplication Project Designer, click the Debug tab.

  4. In the Start Action section, select Start external program. You will be debugging a separate instance of Visual Studio.

  5. Click the ellipsis (VisualStudioEllipsesButton screenshot) button to open the Select File dialog box.

  6. Browse for Visual Studio. The name of the executable file is devenv.exe, and if you installed Visual Studio in the default location, its path is "%programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe".

  7. Click Open to select devenv.exe.

Debugging Your Custom Control at Design Time

Now you are ready to debug your custom control as it runs in design mode. When you start the debugging session, a new instance of Visual Studio will be created, and you will use it to load the AutoSizeButtonLibrary solution. When you open MainWindow.xaml in the WPF Designer, an instance of your custom control will be created and will start to run.

To debug your custom control at design time

  1. Open the AutoSizeButton code file in the Code Editor and add a breakpoint in the constructor.

  2. Open Metadata.cs or Metadata.vb in the Code Editor and add a breakpoint in the AttributeTable property.

  3. Open AutoSizeAdornerProvider.cs or AutoSizeAdornerProvider.vb in the Code Editor and add a breakpoint in the constructor.

  4. Add breakpoints in the remaining methods for the AutoSizeAdornerProvider class.

  5. Press F5 to start the debugging session.

    A second instance of Visual Studio is created. You can distinguish between the debugging instance and the second instance in two ways:

    • The debugging instance has the word Running in its title bar.

    • The debugging instance has the Start button on its Debug toolbar disabled.

    Your breakpoint is set in the debugging instance.

  6. In the second instance of Visual Studio, open the AutoSizeButtonLibrary solution.

  7. Open MainWindow.xaml in the WPF Designer.

    The debugging instance of Visual Studio acquires focus and execution stops at the breakpoint in the AttributeTable property.

  8. Press F5 to continue debugging.

    Execution stops at the breakpoint in the AutoSizeButton constructor.

  9. Press F5 to continue debugging.

    The second instance of Visual Studio acquires focus and the WPF Designer appears.

  10. In Design view, click the AutoSizeButton control to select it.

    The debugging instance of Visual Studio acquires focus and execution stops at the breakpoint in the AutoSizeAdornerProvider constructor.

  11. Press F5 to continue debugging.

    Execution stops at the breakpoint in the Activate method.

  12. Press F5 to continue debugging.

    The second instance of Visual Studio acquires focus and the WPF Designer appears. The check box adorner appears above and to the left of the AutoSizeButton.

  13. When you are finished, you can stop your debugging session by closing the second instance of Visual Studio or by clicking the Stop Debugging button in the debugging instance.

Next Steps

You can add more custom design-time features to your custom controls.

See Also

Tasks

Walkthrough: Debugging a WPF Application

How to: Debug a Designer Load Failure

Reference

PrimarySelectionAdornerProvider

SkewTransform

Concepts

Troubleshooting WPF and Silverlight Designer Load Failures

Other Resources

Advanced Extensibility Concepts

WPF Designer Extensibility