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
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.
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.
Open AutoSizeButton.cs or AutoSizeButton.vb in the Code Editor.
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"; } } } }
Set the project's output path to "bin\".
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
Add a new Class Library project in Visual Basic or Visual C# named AutoSizeButtonLibrary.VisualStudio.Design to the solution.
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.
Add references to the following WPF assemblies.
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
Add references to the following WPF Designer assemblies.
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
Add a reference to the AutoSizeButtonLibrary project.
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.
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(); } } } }
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
Add a new class named AutoSizeAdornerProvider to the AutoSizeButtonLibrary.VisualStudio.Design project.
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; } } } }
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
Add a new WPF Application project named DemoApplication to the solution.
MainWindow.xaml opens in the WPF Designer.
Add a reference to the AutoSizeButtonLibrary project.
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>
In Design view, click the AutoSizeButton control to select it.
A CheckBox control appears above the AutoSizeButton control.
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
In Solution Explorer, right-click the DemoApplication project and select Set as StartUp Project.
In Solution Explorer, right-click the DemoApplication project and select Properties.
In the DemoApplication Project Designer, click the Debug tab.
In the Start Action section, select Start external program. You will be debugging a separate instance of Visual Studio.
Click the ellipsis () button to open the Select File dialog box.
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".
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
Open the AutoSizeButton code file in the Code Editor and add a breakpoint in the constructor.
Open Metadata.cs or Metadata.vb in the Code Editor and add a breakpoint in the AttributeTable property.
Open AutoSizeAdornerProvider.cs or AutoSizeAdornerProvider.vb in the Code Editor and add a breakpoint in the constructor.
Add breakpoints in the remaining methods for the AutoSizeAdornerProvider class.
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.
In the second instance of Visual Studio, open the AutoSizeButtonLibrary solution.
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.
Press F5 to continue debugging.
Execution stops at the breakpoint in the AutoSizeButton constructor.
Press F5 to continue debugging.
The second instance of Visual Studio acquires focus and the WPF Designer appears.
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.
Press F5 to continue debugging.
Execution stops at the breakpoint in the Activate method.
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.
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.
Add a custom adorner to your control's design time. For more information, see Walkthrough: Creating a Design-time Adorner.
Create an editor for a custom type, which you can use in the Properties window. For more information, see How to: Create a Value Editor.
Create a color editor, which you can use in the Properties window. For more information, see Walkthrough: Implementing a Color Editor.
See Also
Tasks
Walkthrough: Debugging a WPF Application
How to: Debug a Designer Load Failure
Reference
PrimarySelectionAdornerProvider
Concepts
Troubleshooting WPF and Silverlight Designer Load Failures