演练:在设计时调试 WPF 自定义控件
本演练演示如何为 Windows Presentation Foundation (WPF) 自定义控件调试设计时装饰器。 该装饰器是一个复选框,提供简单的自动调整大小功能。
在本演练中,您将执行下列任务:
创建一个 WPF 自定义控件库项目。
为设计时元数据创建一个单独的程序集。
实现装饰器提供程序。
在设计时测试控件。
设置项目以便进行设计时调试。
在设计时调试控件。
完成本演练后,您将了解如何为自定义控件调试装饰器。
提示
显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。 若要更改设置,请在“工具”菜单上选择“导入和导出设置”。 有关更多信息,请参见 使用设置。
系统必备
您需要以下组件来完成本演练:
- Visual Studio 2010.
创建自定义控件
第一步是为自定义控件创建项目。 该控件是一个带有少量设计时代码的按钮,该按钮使用 GetIsInDesignMode 方法来实现设计时行为。
创建自定义控件
使用 Visual Basic 或 Visual C# 创建一个名为 AutoSizeButtonLibrary 的新 WPF 自定义控件库项目。
CustomControl1 的代码在“代码编辑器”中打开。
在**“解决方案资源管理器”中,将代码文件的名称更改为 AutoSizeButton.cs 或 AutoSizeButton.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。
在代码编辑器中打开 AutoSizeButton.cs 或 AutoSizeButton.vb。
用下面的代码替换自动生成的代码。 此代码从 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"; } } } }
将项目的输出路径设置为“bin\”。
生成解决方案。
创建设计时元数据程序集
设计时代码在特定元数据程序集中部署。 在本演练中,自定义装饰器部署在一个名为 AutoSizeButtonLibrary.VisualStudio.Design 的程序集中。 有关更多信息,请参见提供设计时元数据。
创建设计时元数据程序集
使用 Visual Basic 或 Visual C# 为解决方案添加一个名为 AutoSizeButtonLibrary.VisualStudio.Design 的新类库项目。
将项目的输出路径设置为“.. \AutoSizeButtonLibrary\bin\”。 这样可使控件的程序集与元数据程序集位于同一文件夹中,从而可为设计器启用元数据发现。
添加对下列 WPF 程序集的引用。
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
添加对下列 WPF 设计器程序集的引用。
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
添加对 AutoSizeButtonLibrary 项目的引用。
在**“解决方案资源管理器”中,将 Class1 代码文件的名称更改为 Metadata.cs 或 Metadata.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。
用下面的代码替换自动生成的代码。 此代码将创建一个将自定义设计时实现附加到 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(); } } } }
保存解决方案。
实现装饰器提供程序
装饰器提供程序是在名为 AutoSizeAdornerProvider 的类型中实现的。 通过此装饰器 FeatureProvider,可以在设计时设置控件的 Height 和 Width 属性。
实现装饰器提供程序
向 AutoSizeButtonLibrary.VisualStudio.Design 项目中添加一个名为 AutoSizeAdornerProvider 的新类。
在 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; } } } }
生成解决方案。
测试设计时实现
可以像使用任何其他 WPF 控件一样使用 AutoSizeButton 控件。 WPF 设计器处理所有设计时对象的创建。
测试设计时实现
向解决方案中添加一个名为 DemoApplication 的新 WPF 应用程序项目。
MainWindow.xaml 将在 WPF 设计器中打开。
添加对 AutoSizeButtonLibrary 项目的引用。
在 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>
在“设计”视图中,单击 AutoSizeButton 控件将其选中。
将在 AutoSizeButton 控件上方出现一个 CheckBox 控件。
取消选中复选框装饰器。
控件变小。 复选框装饰器将移动以维持它与所装饰的控件的相对位置。
设置项目以便进行设计时调试
此时,您已完成设计时实现。 现在,您可以使用 Visual Studio 设置断点并单步执行设计时代码。若要调试设计时实现,请将 Visual Studio 的其他实例附加到当前 Visual Studio 会话。
设置项目以便进行设计时调试
在**“解决方案资源管理器”中右击“DemoApplication”项目,再选择“设为启动项目”**。
在**“解决方案资源管理器”中右击“DemoApplication”项目,然后选择“属性”**。
在**“DemoApplication”项目设计器中单击“调试”**选项卡。
在**“启动操作”部分选择“启动外部程序”**。 您将调试一个单独的 Visual Studio 实例。
单击省略号 () 按钮打开**“选择文件”**对话框。
浏览 Visual Studio。 可执行文件的名称为 devenv.exe,如果将 Visual Studio 安装在默认位置,则其路径是“%programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe”。
单击**“打开”**选择 devenv.exe。
在设计时调试自定义控件
现在,自定义控件以设计模式运行,可以进行调试了。 启动调试会话时,将创建一个新的 Visual Studio 实例,您将使用该实例加载 AutoSizeButtonLibrary 解决方案。 在 WPF 设计器中打开 MainWindow.xaml 时,将会创建自定义控件的一个实例,并将开始运行该实例。
在设计时调试自定义控件
在代码编辑器中打开 AutoSizeButton 代码文件,然后在构造函数中添加断点。
在代码编辑器中打开 Metadata.cs 或 Metadata.vb,并在 AttributeTable 属性中添加断点。
在**“代码编辑器”**中打开 AutoSizeAdornerProvider.cs 或 AutoSizeAdornerProvider.vb,并在构造函数中添加断点。
在 AutoSizeAdornerProvider 类的剩余方法中添加断点。
按 F5 启动调试会话。
此时将创建第二个 Visual Studio 实例。 您可以用两种方法来区分调试实例和第二个实例:
调试实例在其标题栏有**“正在运行”**字样。
调试实例的**“调试”工具栏上的“启动”**按钮被禁用。
断点是在调试实例中设置的。
在 Visual Studio 的第二个实例中,打开 AutoSizeButtonLibrary 解决方案。
在 WPF 设计器中打开 MainWindow.xaml。
Visual Studio 调试实例将获得焦点,并且执行会在 AttributeTable 属性中的断点处停止。
按 F5 继续进行调试。
执行将在 AutoSizeButton 构造函数中的断点处停止。
按 F5 继续进行调试。
Visual Studio 的第二个实例将获得焦点并显示 WPF 设计器。
在“设计”视图中,单击 AutoSizeButton 控件将其选中。
Visual Studio 调试实例将获得焦点,并且执行会在 AutoSizeAdornerProvider 构造函数中的断点处停止。
按 F5 继续进行调试。
执行将在 Activate 方法中的断点处停止。
按 F5 继续进行调试。
Visual Studio 的第二个实例将获得焦点并显示 WPF 设计器。 复选框装饰器将显示在 AutoSizeButton 的上方和左侧。
完成后,可以关闭 Visual Studio 的第二个实例或单击调试实例中的**“停止调试”**按钮来停止调试会话。
后续步骤
您可以向自定义控件中添加更多设计时功能。
向控件的设计时添加自定义装饰器。 有关更多信息,请参见演练:创建设计时装饰器。
为自定义类型创建编辑器,可在“属性”窗口中使用该编辑器。 有关更多信息,请参见如何:创建值编辑器。
创建一个颜色编辑器,可在“属性”窗口中使用该编辑器。 有关更多信息,请参见演练:实现颜色编辑器。
请参见
任务
参考
PrimarySelectionAdornerProvider
概念
WPF 设计器和 Silverlight Designer 加载失败疑难解答