演练:在设计时调试 WPF 自定义控件

本演练演示如何为 Windows Presentation Foundation (WPF) 自定义控件调试设计时装饰器。 该装饰器是一个复选框,提供简单的自动调整大小功能。

在本演练中,您将执行下列任务:

  • 创建一个 WPF 自定义控件库项目。

  • 为设计时元数据创建一个单独的程序集。

  • 实现装饰器提供程序。

  • 在设计时测试控件。

  • 设置项目以便进行设计时调试。

  • 在设计时调试控件。

完成本演练后,您将了解如何为自定义控件调试装饰器。

提示

显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。 若要更改设置,请在“工具”菜单上选择“导入和导出设置”。 有关更多信息,请参见 使用设置

系统必备

您需要以下组件来完成本演练:

  • Visual Studio 2010.

创建自定义控件

第一步是为自定义控件创建项目。 该控件是一个带有少量设计时代码的按钮,该按钮使用 GetIsInDesignMode 方法来实现设计时行为。

创建自定义控件

  1. 使用 Visual Basic 或 Visual C# 创建一个名为 AutoSizeButtonLibrary 的新 WPF 自定义控件库项目。

    CustomControl1 的代码在“代码编辑器”中打开。

  2. 在**“解决方案资源管理器”中,将代码文件的名称更改为 AutoSizeButton.cs 或 AutoSizeButton.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。

  3. 在代码编辑器中打开 AutoSizeButton.cs 或 AutoSizeButton.vb。

  4. 用下面的代码替换自动生成的代码。 此代码从 Button 继承而来,当按钮出现在设计器中时,将显示文本“已激活设计模式”。

    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. 将项目的输出路径设置为“bin\”。

  6. 生成解决方案。

创建设计时元数据程序集

设计时代码在特定元数据程序集中部署。 在本演练中,自定义装饰器部署在一个名为 AutoSizeButtonLibrary.VisualStudio.Design 的程序集中。 有关更多信息,请参见提供设计时元数据

创建设计时元数据程序集

  1. 使用 Visual Basic 或 Visual C# 为解决方案添加一个名为 AutoSizeButtonLibrary.VisualStudio.Design 的新类库项目。

  2. 将项目的输出路径设置为“.. \AutoSizeButtonLibrary\bin\”。 这样可使控件的程序集与元数据程序集位于同一文件夹中,从而可为设计器启用元数据发现。

  3. 添加对下列 WPF 程序集的引用。

    • PresentationCore

    • PresentationFramework

    • System.Xaml

    • WindowsBase

  4. 添加对下列 WPF 设计器程序集的引用。

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. 添加对 AutoSizeButtonLibrary 项目的引用。

  6. 在**“解决方案资源管理器”中,将 Class1 代码文件的名称更改为 Metadata.cs 或 Metadata.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。

  7. 用下面的代码替换自动生成的代码。 此代码将创建一个将自定义设计时实现附加到 AutoSizeButton 类的 AttributeTable

    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. 保存解决方案。

实现装饰器提供程序

装饰器提供程序是在名为 AutoSizeAdornerProvider 的类型中实现的。 通过此装饰器 FeatureProvider,可以在设计时设置控件的 HeightWidth 属性。

实现装饰器提供程序

  1. 向 AutoSizeButtonLibrary.VisualStudio.Design 项目中添加一个名为 AutoSizeAdornerProvider 的新类。

  2. 在 AutoSizeAdornerProvider 的代码编辑器中,用下面的代码替换自动生成的代码。 此代码实现的 PrimarySelectionAdornerProvider 提供一个基于 CheckBox 控件的装饰器。

    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. 生成解决方案。

测试设计时实现

可以像使用任何其他 WPF 控件一样使用 AutoSizeButton 控件。 WPF 设计器处理所有设计时对象的创建。

测试设计时实现

  1. 向解决方案中添加一个名为 DemoApplication 的新 WPF 应用程序项目。

    MainWindow.xaml 将在 WPF 设计器中打开。

  2. 添加对 AutoSizeButtonLibrary 项目的引用。

  3. 在 XAML 视图中,用以下代码替换自动生成的代码。 此 XAML 将添加对 AutoSizeButtonLibrary 命名空间的引用并添加 AutoSizeButton 自定义控件。 该按钮出现在“设计”视图中,并显示文本“已激活设计模式”,表明该按钮处于设计模式下。 如果该按钮未出现,则可能需要单击设计器顶部的信息栏以重新加载视图。

    <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. 在“设计”视图中,单击 AutoSizeButton 控件将其选中。

    将在 AutoSizeButton 控件上方出现一个 CheckBox 控件。

  5. 取消选中复选框装饰器。

    控件变小。 复选框装饰器将移动以维持它与所装饰的控件的相对位置。

设置项目以便进行设计时调试

此时,您已完成设计时实现。 现在,您可以使用 Visual Studio 设置断点并单步执行设计时代码。若要调试设计时实现,请将 Visual Studio 的其他实例附加到当前 Visual Studio 会话。

设置项目以便进行设计时调试

  1. 在**“解决方案资源管理器”中右击“DemoApplication”项目,再选择“设为启动项目”**。

  2. 在**“解决方案资源管理器”中右击“DemoApplication”项目,然后选择“属性”**。

  3. 在**“DemoApplication”项目设计器中单击“调试”**选项卡。

  4. 在**“启动操作”部分选择“启动外部程序”**。 您将调试一个单独的 Visual Studio 实例。

  5. 单击省略号 (VisualStudioEllipsesButton 屏幕快照) 按钮打开**“选择文件”**对话框。

  6. 浏览 Visual Studio。 可执行文件的名称为 devenv.exe,如果将 Visual Studio 安装在默认位置,则其路径是“%programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe”。

  7. 单击**“打开”**选择 devenv.exe。

在设计时调试自定义控件

现在,自定义控件以设计模式运行,可以进行调试了。 启动调试会话时,将创建一个新的 Visual Studio 实例,您将使用该实例加载 AutoSizeButtonLibrary 解决方案。 在 WPF 设计器中打开 MainWindow.xaml 时,将会创建自定义控件的一个实例,并将开始运行该实例。

在设计时调试自定义控件

  1. 在代码编辑器中打开 AutoSizeButton 代码文件,然后在构造函数中添加断点。

  2. 在代码编辑器中打开 Metadata.cs 或 Metadata.vb,并在 AttributeTable 属性中添加断点。

  3. 在**“代码编辑器”**中打开 AutoSizeAdornerProvider.cs 或 AutoSizeAdornerProvider.vb,并在构造函数中添加断点。

  4. 在 AutoSizeAdornerProvider 类的剩余方法中添加断点。

  5. 按 F5 启动调试会话。

    此时将创建第二个 Visual Studio 实例。 您可以用两种方法来区分调试实例和第二个实例:

    • 调试实例在其标题栏有**“正在运行”**字样。

    • 调试实例的**“调试”工具栏上的“启动”**按钮被禁用。

    断点是在调试实例中设置的。

  6. 在 Visual Studio 的第二个实例中,打开 AutoSizeButtonLibrary 解决方案。

  7. 在 WPF 设计器中打开 MainWindow.xaml。

    Visual Studio 调试实例将获得焦点,并且执行会在 AttributeTable 属性中的断点处停止。

  8. 按 F5 继续进行调试。

    执行将在 AutoSizeButton 构造函数中的断点处停止。

  9. 按 F5 继续进行调试。

    Visual Studio 的第二个实例将获得焦点并显示 WPF 设计器。

  10. 在“设计”视图中,单击 AutoSizeButton 控件将其选中。

    Visual Studio 调试实例将获得焦点,并且执行会在 AutoSizeAdornerProvider 构造函数中的断点处停止。

  11. 按 F5 继续进行调试。

    执行将在 Activate 方法中的断点处停止。

  12. 按 F5 继续进行调试。

    Visual Studio 的第二个实例将获得焦点并显示 WPF 设计器。 复选框装饰器将显示在 AutoSizeButton 的上方和左侧。

  13. 完成后,可以关闭 Visual Studio 的第二个实例或单击调试实例中的**“停止调试”**按钮来停止调试会话。

后续步骤

您可以向自定义控件中添加更多设计时功能。

请参见

任务

演练:调试 WPF 应用程序

参考

PrimarySelectionAdornerProvider

SkewTransform

概念

WPF 设计器和 Silverlight Designer 加载失败疑难解答

其他资源

如何:调试设计器加载失败问题

高级扩展性概念

WPF 设计器扩展性